mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
@@ -20,7 +20,7 @@ import 'dart:math' as math;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide DraggableScrollableSheet;
|
||||
|
||||
/// Controls a [DraggableScrollableSheet].
|
||||
///
|
||||
@@ -112,11 +112,10 @@ class DraggableScrollableController extends ChangeNotifier {
|
||||
_assertAttached();
|
||||
assert(size >= 0 && size <= 1);
|
||||
assert(duration != Duration.zero);
|
||||
final AnimationController animationController =
|
||||
AnimationController.unbounded(
|
||||
vsync: _attachedController!.position.context.vsync,
|
||||
value: _attachedController!.extent.currentSize,
|
||||
);
|
||||
final animationController = AnimationController.unbounded(
|
||||
vsync: _attachedController!.position.context.vsync,
|
||||
value: _attachedController!.extent.currentSize,
|
||||
);
|
||||
_animationControllers.add(animationController);
|
||||
_attachedController!.position.goIdle();
|
||||
// This disables any snapping until the next user interaction with the sheet.
|
||||
@@ -583,7 +582,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
}
|
||||
|
||||
List<double> _impliedSnapSizes() {
|
||||
for (int index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) {
|
||||
for (var index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) {
|
||||
final double snapSize = widget.snapSizes![index];
|
||||
assert(
|
||||
snapSize >= widget.minChildSize && snapSize <= widget.maxChildSize,
|
||||
@@ -684,11 +683,11 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
// have changed when the widget was updated.
|
||||
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
|
||||
for (
|
||||
int index = 0;
|
||||
var index = 0;
|
||||
index < _scrollController.positions.length;
|
||||
index++
|
||||
) {
|
||||
final _DraggableScrollableSheetScrollPosition position =
|
||||
final position =
|
||||
_scrollController.positions.elementAt(index)
|
||||
as _DraggableScrollableSheetScrollPosition;
|
||||
position.goBallistic(0);
|
||||
@@ -702,7 +701,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
.asMap()
|
||||
.keys
|
||||
.map((int index) {
|
||||
final String snapSizeString = widget.snapSizes![index].toString();
|
||||
final snapSizeString = widget.snapSizes![index].toString();
|
||||
if (index == invalidIndex) {
|
||||
return '>>> $snapSizeString <<<';
|
||||
}
|
||||
@@ -917,14 +916,10 @@ class _DraggableScrollableSheetScrollPosition
|
||||
);
|
||||
}
|
||||
|
||||
final AnimationController ballisticController =
|
||||
AnimationController.unbounded(
|
||||
debugLabel: objectRuntimeType(
|
||||
this,
|
||||
'_DraggableScrollableSheetPosition',
|
||||
),
|
||||
vsync: context.vsync,
|
||||
);
|
||||
final ballisticController = AnimationController.unbounded(
|
||||
debugLabel: objectRuntimeType(this, '_DraggableScrollableSheetPosition'),
|
||||
vsync: context.vsync,
|
||||
);
|
||||
_ballisticControllers.add(ballisticController);
|
||||
|
||||
double lastPosition = extent.currentPixels;
|
||||
@@ -1080,8 +1075,7 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> {
|
||||
return false;
|
||||
}
|
||||
assert(widget is _InheritedResetNotifier);
|
||||
final _InheritedResetNotifier inheritedNotifier =
|
||||
widget as _InheritedResetNotifier;
|
||||
final inheritedNotifier = widget as _InheritedResetNotifier;
|
||||
final bool wasCalled = inheritedNotifier.notifier!._wasCalled;
|
||||
inheritedNotifier.notifier!._wasCalled = false;
|
||||
return wasCalled;
|
||||
@@ -1158,6 +1152,10 @@ class _SnappingSimulation extends Simulation {
|
||||
return pixelSnapSizes.first;
|
||||
}
|
||||
final double nextSize = pixelSnapSizes[indexOfNextSize];
|
||||
// If already snapped - keep this as target size
|
||||
if (nextSize == position) {
|
||||
return nextSize;
|
||||
}
|
||||
final double previousSize = pixelSnapSizes[indexOfNextSize - 1];
|
||||
if (initialVelocity.abs() <= tolerance.velocity) {
|
||||
// If velocity is zero, snap to the nearest snap size with the minimum velocity.
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'dart:math' as math;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide DraggableScrollableSheet;
|
||||
|
||||
/// Controls a [DraggableScrollableSheet].
|
||||
///
|
||||
@@ -112,11 +112,10 @@ class DraggableScrollableController extends ChangeNotifier {
|
||||
_assertAttached();
|
||||
assert(size >= 0 && size <= 1);
|
||||
assert(duration != Duration.zero);
|
||||
final AnimationController animationController =
|
||||
AnimationController.unbounded(
|
||||
vsync: _attachedController!.position.context.vsync,
|
||||
value: _attachedController!.extent.currentSize,
|
||||
);
|
||||
final animationController = AnimationController.unbounded(
|
||||
vsync: _attachedController!.position.context.vsync,
|
||||
value: _attachedController!.extent.currentSize,
|
||||
);
|
||||
_animationControllers.add(animationController);
|
||||
_attachedController!.position.goIdle();
|
||||
// This disables any snapping until the next user interaction with the sheet.
|
||||
@@ -587,7 +586,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
}
|
||||
|
||||
List<double> _impliedSnapSizes() {
|
||||
for (int index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) {
|
||||
for (var index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) {
|
||||
final double snapSize = widget.snapSizes![index];
|
||||
assert(
|
||||
snapSize >= widget.minChildSize && snapSize <= widget.maxChildSize,
|
||||
@@ -688,11 +687,11 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
// have changed when the widget was updated.
|
||||
WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) {
|
||||
for (
|
||||
int index = 0;
|
||||
var index = 0;
|
||||
index < _scrollController.positions.length;
|
||||
index++
|
||||
) {
|
||||
final _DraggableScrollableSheetScrollPosition position =
|
||||
final position =
|
||||
_scrollController.positions.elementAt(index)
|
||||
as _DraggableScrollableSheetScrollPosition;
|
||||
position.goBallistic(0);
|
||||
@@ -706,7 +705,7 @@ class _DraggableScrollableSheetState extends State<DraggableScrollableSheet> {
|
||||
.asMap()
|
||||
.keys
|
||||
.map((int index) {
|
||||
final String snapSizeString = widget.snapSizes![index].toString();
|
||||
final snapSizeString = widget.snapSizes![index].toString();
|
||||
if (index == invalidIndex) {
|
||||
return '>>> $snapSizeString <<<';
|
||||
}
|
||||
@@ -920,14 +919,10 @@ class _DraggableScrollableSheetScrollPosition
|
||||
);
|
||||
}
|
||||
|
||||
final AnimationController ballisticController =
|
||||
AnimationController.unbounded(
|
||||
debugLabel: objectRuntimeType(
|
||||
this,
|
||||
'_DraggableScrollableSheetPosition',
|
||||
),
|
||||
vsync: context.vsync,
|
||||
);
|
||||
final ballisticController = AnimationController.unbounded(
|
||||
debugLabel: objectRuntimeType(this, '_DraggableScrollableSheetPosition'),
|
||||
vsync: context.vsync,
|
||||
);
|
||||
_ballisticControllers.add(ballisticController);
|
||||
|
||||
double lastPosition = extent.currentPixels;
|
||||
@@ -1082,8 +1077,7 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> {
|
||||
return false;
|
||||
}
|
||||
assert(widget is _InheritedResetNotifier);
|
||||
final _InheritedResetNotifier inheritedNotifier =
|
||||
widget as _InheritedResetNotifier;
|
||||
final inheritedNotifier = widget as _InheritedResetNotifier;
|
||||
final bool wasCalled = inheritedNotifier.notifier!._wasCalled;
|
||||
inheritedNotifier.notifier!._wasCalled = false;
|
||||
return wasCalled;
|
||||
@@ -1160,6 +1154,10 @@ class _SnappingSimulation extends Simulation {
|
||||
return pixelSnapSizes.first;
|
||||
}
|
||||
final double nextSize = pixelSnapSizes[indexOfNextSize];
|
||||
// If already snapped - keep this as target size
|
||||
if (nextSize == position) {
|
||||
return nextSize;
|
||||
}
|
||||
final double previousSize = pixelSnapSizes[indexOfNextSize - 1];
|
||||
if (initialVelocity.abs() <= tolerance.velocity) {
|
||||
// If velocity is zero, snap to the nearest snap size with the minimum velocity.
|
||||
|
||||
@@ -15,7 +15,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/dyn/ink_well.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide InkWell;
|
||||
import 'package:flutter/material.dart' hide ButtonStyleButton, InkWell;
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
/// The base [StatefulWidget] class for buttons whose style is defined by a [ButtonStyle] object.
|
||||
@@ -121,7 +121,7 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
||||
/// Defaults to true.
|
||||
final bool? isSemanticButton;
|
||||
|
||||
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||
/// {@macro flutter.material.ButtonStyle.iconAlignment}
|
||||
@Deprecated(
|
||||
'Remove this parameter as it is now ignored. '
|
||||
'Use ButtonStyle.iconAlignment instead. '
|
||||
@@ -722,9 +722,9 @@ class _RenderInputPadding extends RenderShiftedBox {
|
||||
}) {
|
||||
if (child != null) {
|
||||
final Size childSize = layoutChild(child!, constraints);
|
||||
final double height = math.max(childSize.width, minSize.width);
|
||||
final double width = math.max(childSize.height, minSize.height);
|
||||
return constraints.constrain(Size(height, width));
|
||||
final double width = math.max(childSize.width, minSize.width);
|
||||
final double height = math.max(childSize.height, minSize.height);
|
||||
return constraints.constrain(Size(width, height));
|
||||
}
|
||||
return Size.zero;
|
||||
}
|
||||
@@ -764,7 +764,7 @@ class _RenderInputPadding extends RenderShiftedBox {
|
||||
layoutChild: ChildLayoutHelper.layoutChild,
|
||||
);
|
||||
if (child != null) {
|
||||
final BoxParentData childParentData = child!.parentData! as BoxParentData;
|
||||
final childParentData = child!.parentData! as BoxParentData;
|
||||
childParentData.offset = Alignment.center.alongOffset(
|
||||
size - child!.size as Offset,
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'dart:collection';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide InkWell;
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
abstract class _ParentInkResponseState {
|
||||
@@ -265,14 +265,16 @@ class InkResponse extends StatelessWidget {
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||
/// widget.
|
||||
///
|
||||
/// {@template flutter.material.InkWell.mouseCursor}
|
||||
/// If [mouseCursor] is a [WidgetStateMouseCursor],
|
||||
/// [WidgetStateProperty.resolve] is used for the following [WidgetState]s:
|
||||
///
|
||||
/// * [WidgetState.hovered].
|
||||
/// * [WidgetState.focused].
|
||||
/// * [WidgetState.disabled].
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// If this property is null, [WidgetStateMouseCursor.clickable] will be used.
|
||||
/// If this property is null, [WidgetStateMouseCursor.adaptiveClickable] will be used.
|
||||
final MouseCursor? mouseCursor;
|
||||
|
||||
/// Whether this ink response should be clipped its bounds.
|
||||
@@ -638,7 +640,7 @@ class _InkResponseStateWidget extends StatefulWidget {
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
final List<String> gestures = <String>[
|
||||
final gestures = <String>[
|
||||
if (onTap != null) 'tap',
|
||||
if (onDoubleTap != null) 'double tap',
|
||||
if (onLongPress != null) 'long press',
|
||||
@@ -900,7 +902,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
_HighlightType.hover =>
|
||||
widget.hoverColor ?? Theme.of(context).hoverColor,
|
||||
};
|
||||
final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
|
||||
final referenceBox = context.findRenderObject()! as RenderBox;
|
||||
_highlights[type] = InkHighlight(
|
||||
controller: Material.of(context),
|
||||
referenceBox: referenceBox,
|
||||
@@ -951,7 +953,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
|
||||
InteractiveInkFeature _createSplash(Offset globalPosition) {
|
||||
final MaterialInkController inkController = Material.of(context);
|
||||
final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
|
||||
final referenceBox = context.findRenderObject()! as RenderBox;
|
||||
final Offset position = referenceBox.globalToLocal(globalPosition);
|
||||
final Color color =
|
||||
widget.overlayColor?.resolve(statesController.value) ??
|
||||
@@ -1054,7 +1056,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
|
||||
final Offset globalPosition;
|
||||
if (context != null) {
|
||||
final RenderBox referenceBox = context.findRenderObject()! as RenderBox;
|
||||
final referenceBox = context.findRenderObject()! as RenderBox;
|
||||
assert(
|
||||
referenceBox.hasSize,
|
||||
'InkResponse must be done with layout before starting a splash.',
|
||||
@@ -1139,7 +1141,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
if (_splashes != null) {
|
||||
final Set<InteractiveInkFeature> splashes = _splashes!;
|
||||
_splashes = null;
|
||||
for (final InteractiveInkFeature splash in splashes) {
|
||||
for (final splash in splashes) {
|
||||
splash.dispose();
|
||||
}
|
||||
_currentSplash = null;
|
||||
@@ -1205,7 +1207,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
assert(widget.debugCheckContext(context));
|
||||
|
||||
final ThemeData theme = Theme.of(context);
|
||||
const Set<WidgetState> highlightableStates = <WidgetState>{
|
||||
const highlightableStates = <WidgetState>{
|
||||
WidgetState.focused,
|
||||
WidgetState.hovered,
|
||||
WidgetState.pressed,
|
||||
@@ -1216,15 +1218,15 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
);
|
||||
// Each highlightable state will be resolved separately to get the corresponding color.
|
||||
// For this resolution to be correct, the non-highlightable states should be preserved.
|
||||
final Set<WidgetState> pressed = <WidgetState>{
|
||||
final pressed = <WidgetState>{
|
||||
...nonHighlightableStates,
|
||||
WidgetState.pressed,
|
||||
};
|
||||
final Set<WidgetState> focused = <WidgetState>{
|
||||
final focused = <WidgetState>{
|
||||
...nonHighlightableStates,
|
||||
WidgetState.focused,
|
||||
};
|
||||
final Set<WidgetState> hovered = <WidgetState>{
|
||||
final hovered = <WidgetState>{
|
||||
...nonHighlightableStates,
|
||||
WidgetState.hovered,
|
||||
};
|
||||
@@ -1260,7 +1262,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
||||
|
||||
final MouseCursor effectiveMouseCursor =
|
||||
WidgetStateProperty.resolveAs<MouseCursor>(
|
||||
widget.mouseCursor ?? WidgetStateMouseCursor.clickable,
|
||||
widget.mouseCursor ?? WidgetStateMouseCursor.adaptiveClickable,
|
||||
statesController.value,
|
||||
);
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/dyn/button.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide InkWell, ButtonStyleButton;
|
||||
import 'package:flutter/material.dart'
|
||||
hide ButtonStyleButton, TextButton, InkWell;
|
||||
|
||||
/// A Material Design "Text Button".
|
||||
///
|
||||
@@ -84,7 +85,7 @@ class TextButton extends ButtonStyleButton {
|
||||
super.statesController,
|
||||
super.isSemanticButton,
|
||||
required Widget super.child,
|
||||
});
|
||||
}) : _addPadding = false;
|
||||
|
||||
/// Create a text button from a pair of widgets that serve as the button's
|
||||
/// [icon] and [label].
|
||||
@@ -92,56 +93,38 @@ class TextButton extends ButtonStyleButton {
|
||||
/// The icon and label are arranged in a row and padded by 8 logical pixels
|
||||
/// at the ends, with an 8 pixel gap in between.
|
||||
///
|
||||
/// If [icon] is null, will create a [TextButton] instead.
|
||||
/// If [icon] is null, this constructor will create a [TextButton]
|
||||
/// that doesn't display an icon.
|
||||
///
|
||||
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||||
/// {@macro flutter.material.ButtonStyle.iconAlignment}
|
||||
///
|
||||
factory TextButton.icon({
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
Clip? clipBehavior,
|
||||
WidgetStatesController? statesController,
|
||||
TextButton.icon({
|
||||
super.key,
|
||||
required super.onPressed,
|
||||
super.onLongPress,
|
||||
super.onHover,
|
||||
super.onFocusChange,
|
||||
super.style,
|
||||
super.focusNode,
|
||||
super.autofocus = false,
|
||||
super.clipBehavior = Clip.none,
|
||||
super.statesController,
|
||||
Widget? icon,
|
||||
required Widget label,
|
||||
IconAlignment? iconAlignment,
|
||||
}) {
|
||||
if (icon == null) {
|
||||
return TextButton(
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus ?? false,
|
||||
clipBehavior: clipBehavior ?? Clip.none,
|
||||
statesController: statesController,
|
||||
child: label,
|
||||
);
|
||||
}
|
||||
return _TextButtonWithIcon(
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus ?? false,
|
||||
clipBehavior: clipBehavior ?? Clip.none,
|
||||
statesController: statesController,
|
||||
icon: icon,
|
||||
label: label,
|
||||
iconAlignment: iconAlignment,
|
||||
);
|
||||
}
|
||||
}) : _addPadding = icon != null,
|
||||
super(
|
||||
child: icon != null
|
||||
? _TextButtonWithIconChild(
|
||||
label: label,
|
||||
icon: icon,
|
||||
buttonStyle: style,
|
||||
iconAlignment: iconAlignment,
|
||||
)
|
||||
: label,
|
||||
);
|
||||
|
||||
final bool _addPadding;
|
||||
|
||||
/// A static convenience method that constructs a text button
|
||||
/// [ButtonStyle] given simple values.
|
||||
@@ -339,9 +322,7 @@ class TextButton extends ButtonStyleButton {
|
||||
/// * `maximumSize` - Size.infinite
|
||||
/// * `side` - null
|
||||
/// * `shape` - RoundedRectangleBorder(borderRadius: BorderRadius.circular(4))
|
||||
/// * `mouseCursor`
|
||||
/// * disabled - SystemMouseCursors.basic
|
||||
/// * others - SystemMouseCursors.click
|
||||
/// * `mouseCursor` - WidgetStateMouseCursor.adaptiveClickable
|
||||
/// * `visualDensity` - theme.visualDensity
|
||||
/// * `tapTargetSize` - theme.materialTapTargetSize
|
||||
/// * `animationDuration` - kThemeChangeDuration
|
||||
@@ -389,9 +370,7 @@ class TextButton extends ButtonStyleButton {
|
||||
/// * `maximumSize` - Size.infinite
|
||||
/// * `side` - null
|
||||
/// * `shape` - StadiumBorder()
|
||||
/// * `mouseCursor`
|
||||
/// * disabled - SystemMouseCursors.basic
|
||||
/// * others - SystemMouseCursors.click
|
||||
/// * `mouseCursor` - WidgetStateMouseCursor.adaptiveClickable
|
||||
/// * `visualDensity` - theme.visualDensity
|
||||
/// * `tapTargetSize` - theme.materialTapTargetSize
|
||||
/// * `animationDuration` - kThemeChangeDuration
|
||||
@@ -406,8 +385,7 @@ class TextButton extends ButtonStyleButton {
|
||||
ButtonStyle defaultStyleOf(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final ColorScheme colorScheme = theme.colorScheme;
|
||||
|
||||
return Theme.of(context).useMaterial3
|
||||
final ButtonStyle buttonStyle = theme.useMaterial3
|
||||
? _TextButtonDefaultsM3(context)
|
||||
: styleFrom(
|
||||
foregroundColor: colorScheme.primary,
|
||||
@@ -425,7 +403,9 @@ class TextButton extends ButtonStyleButton {
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
enabledMouseCursor: SystemMouseCursors.click,
|
||||
enabledMouseCursor: kIsWeb
|
||||
? SystemMouseCursors.click
|
||||
: SystemMouseCursors.basic,
|
||||
disabledMouseCursor: SystemMouseCursors.basic,
|
||||
visualDensity: theme.visualDensity,
|
||||
tapTargetSize: theme.materialTapTargetSize,
|
||||
@@ -434,6 +414,28 @@ class TextButton extends ButtonStyleButton {
|
||||
alignment: Alignment.center,
|
||||
splashFactory: InkRipple.splashFactory,
|
||||
);
|
||||
|
||||
// Only apply padding when TextButton has an Icon.
|
||||
if (_addPadding) {
|
||||
final double defaultFontSize =
|
||||
buttonStyle.textStyle?.resolve(const <WidgetState>{})?.fontSize ??
|
||||
14.0;
|
||||
final double effectiveTextScale =
|
||||
MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0;
|
||||
final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
|
||||
theme.useMaterial3
|
||||
? const EdgeInsetsDirectional.fromSTEB(12, 8, 16, 8)
|
||||
: const EdgeInsets.all(8),
|
||||
const EdgeInsets.symmetric(horizontal: 4),
|
||||
const EdgeInsets.symmetric(horizontal: 4),
|
||||
effectiveTextScale,
|
||||
);
|
||||
return buttonStyle.copyWith(
|
||||
padding: WidgetStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
|
||||
);
|
||||
}
|
||||
|
||||
return buttonStyle;
|
||||
}
|
||||
|
||||
/// Returns the [TextButtonThemeData.style] of the closest
|
||||
@@ -459,53 +461,6 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) {
|
||||
);
|
||||
}
|
||||
|
||||
class _TextButtonWithIcon extends TextButton {
|
||||
_TextButtonWithIcon({
|
||||
super.key,
|
||||
required super.onPressed,
|
||||
super.onLongPress,
|
||||
super.onHover,
|
||||
super.onFocusChange,
|
||||
super.style,
|
||||
super.focusNode,
|
||||
bool? autofocus,
|
||||
super.clipBehavior,
|
||||
super.statesController,
|
||||
required Widget icon,
|
||||
required Widget label,
|
||||
IconAlignment? iconAlignment,
|
||||
}) : super(
|
||||
autofocus: autofocus ?? false,
|
||||
child: _TextButtonWithIconChild(
|
||||
icon: icon,
|
||||
label: label,
|
||||
buttonStyle: style,
|
||||
iconAlignment: iconAlignment,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
ButtonStyle defaultStyleOf(BuildContext context) {
|
||||
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||||
final ButtonStyle buttonStyle = super.defaultStyleOf(context);
|
||||
final double defaultFontSize =
|
||||
buttonStyle.textStyle?.resolve(const <WidgetState>{})?.fontSize ?? 14.0;
|
||||
final double effectiveTextScale =
|
||||
MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0;
|
||||
final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding(
|
||||
useMaterial3
|
||||
? const EdgeInsetsDirectional.fromSTEB(12, 8, 16, 8)
|
||||
: const EdgeInsets.all(8),
|
||||
const EdgeInsets.symmetric(horizontal: 4),
|
||||
const EdgeInsets.symmetric(horizontal: 4),
|
||||
effectiveTextScale,
|
||||
);
|
||||
return buttonStyle.copyWith(
|
||||
padding: WidgetStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TextButtonWithIconChild extends StatelessWidget {
|
||||
const _TextButtonWithIconChild({
|
||||
required this.label,
|
||||
@@ -557,72 +512,72 @@ class _TextButtonWithIconChild extends StatelessWidget {
|
||||
// dart format off
|
||||
class _TextButtonDefaultsM3 extends ButtonStyle {
|
||||
_TextButtonDefaultsM3(this.context)
|
||||
: super(
|
||||
animationDuration: kThemeChangeDuration,
|
||||
enableFeedback: true,
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
: super(
|
||||
animationDuration: kThemeChangeDuration,
|
||||
enableFeedback: true,
|
||||
alignment: Alignment.center,
|
||||
);
|
||||
|
||||
final BuildContext context;
|
||||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||||
|
||||
@override
|
||||
WidgetStateProperty<TextStyle?> get textStyle =>
|
||||
WidgetStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
|
||||
WidgetStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color?>? get backgroundColor =>
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color?>? get foregroundColor =>
|
||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return _colors.onSurface.withValues(alpha: 0.38);
|
||||
}
|
||||
return _colors.primary;
|
||||
});
|
||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return _colors.onSurface.withValues(alpha: 0.38);
|
||||
}
|
||||
return _colors.primary;
|
||||
});
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color?>? get overlayColor =>
|
||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return _colors.primary.withValues(alpha: 0.1);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return _colors.primary.withValues(alpha: 0.08);
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return _colors.primary.withValues(alpha: 0.1);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.pressed)) {
|
||||
return _colors.primary.withValues(alpha: 0.1);
|
||||
}
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return _colors.primary.withValues(alpha: 0.08);
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return _colors.primary.withValues(alpha: 0.1);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color>? get shadowColor =>
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color>? get surfaceTintColor =>
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
const WidgetStatePropertyAll<Color>(Colors.transparent);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<double>? get elevation =>
|
||||
const WidgetStatePropertyAll<double>(0.0);
|
||||
const WidgetStatePropertyAll<double>(0.0);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<EdgeInsetsGeometry>? get padding =>
|
||||
WidgetStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
|
||||
WidgetStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Size>? get minimumSize =>
|
||||
const WidgetStatePropertyAll<Size>(Size(64.0, 40.0));
|
||||
const WidgetStatePropertyAll<Size>(Size(64.0, 40.0));
|
||||
|
||||
// No default fixedSize
|
||||
|
||||
@override
|
||||
WidgetStateProperty<double>? get iconSize =>
|
||||
const WidgetStatePropertyAll<double>(18.0);
|
||||
const WidgetStatePropertyAll<double>(18.0);
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Color>? get iconColor {
|
||||
@@ -645,22 +600,16 @@ class _TextButtonDefaultsM3 extends ButtonStyle {
|
||||
|
||||
@override
|
||||
WidgetStateProperty<Size>? get maximumSize =>
|
||||
const WidgetStatePropertyAll<Size>(Size.infinite);
|
||||
const WidgetStatePropertyAll<Size>(Size.infinite);
|
||||
|
||||
// No default side
|
||||
|
||||
@override
|
||||
WidgetStateProperty<OutlinedBorder>? get shape =>
|
||||
const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder());
|
||||
const WidgetStatePropertyAll<OutlinedBorder>(StadiumBorder());
|
||||
|
||||
@override
|
||||
WidgetStateProperty<MouseCursor?>? get mouseCursor =>
|
||||
WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
||||
if (states.contains(WidgetState.disabled)) {
|
||||
return SystemMouseCursors.basic;
|
||||
}
|
||||
return SystemMouseCursors.click;
|
||||
});
|
||||
WidgetStateProperty<MouseCursor?>? get mouseCursor => WidgetStateMouseCursor.adaptiveClickable;
|
||||
|
||||
@override
|
||||
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
||||
|
||||
@@ -20,7 +20,7 @@ library;
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide ListTile;
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
// Examples can assume:
|
||||
@@ -1506,11 +1506,16 @@ class _RenderListTile extends RenderBox
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) {
|
||||
return math.max(
|
||||
_targetTileHeight,
|
||||
title.getMinIntrinsicHeight(width) +
|
||||
(subtitle?.getMinIntrinsicHeight(width) ?? 0.0),
|
||||
);
|
||||
final double titleMinHeight = title.getMinIntrinsicHeight(width);
|
||||
final double? subtitleMinHeight = subtitle?.getMinIntrinsicHeight(width);
|
||||
|
||||
const topAndBottomPaddingMultiplier = 2;
|
||||
final double contentHeight =
|
||||
titleMinHeight +
|
||||
(subtitleMinHeight ?? 0.0) +
|
||||
topAndBottomPaddingMultiplier * _minVerticalPadding;
|
||||
|
||||
return math.max(_targetTileHeight, contentHeight);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,18 +2,11 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: uri_does_not_exist_in_doc_import
|
||||
|
||||
/// @docImport 'package:flutter/material.dart';
|
||||
///
|
||||
/// @docImport 'single_child_scroll_view.dart';
|
||||
/// @docImport 'text.dart';
|
||||
library;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||
import 'package:flutter/gestures.dart'
|
||||
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
||||
import 'package:flutter/material.dart'
|
||||
hide PageView, Scrollable, ScrollableState;
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class _ForceImplicitScrollPhysics extends ScrollPhysics {
|
||||
@@ -378,7 +371,7 @@ class _PageViewState<T extends HorizontalDragGestureRecognizer>
|
||||
if (notification.depth == 0 &&
|
||||
widget.onPageChanged != null &&
|
||||
notification is ScrollUpdateNotification) {
|
||||
final PageMetrics metrics = notification.metrics as PageMetrics;
|
||||
final metrics = notification.metrics as PageMetrics;
|
||||
final int currentPage = metrics.page!.round();
|
||||
if (currentPage != _lastReportedPage) {
|
||||
_lastReportedPage = currentPage;
|
||||
|
||||
@@ -2,20 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: uri_does_not_exist_in_doc_import
|
||||
|
||||
/// @docImport 'package:flutter/material.dart';
|
||||
///
|
||||
/// @docImport 'page_storage.dart';
|
||||
/// @docImport 'page_view.dart';
|
||||
/// @docImport 'scroll_metrics.dart';
|
||||
/// @docImport 'scroll_notification.dart';
|
||||
/// @docImport 'scroll_view.dart';
|
||||
/// @docImport 'single_child_scroll_view.dart';
|
||||
/// @docImport 'two_dimensional_scroll_view.dart';
|
||||
/// @docImport 'two_dimensional_viewport.dart';
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
@@ -350,7 +336,7 @@ class Scrollable<T extends HorizontalDragGestureRecognizer>
|
||||
/// if no [Scrollable] ancestor is found.
|
||||
static ScrollableState? maybeOf(BuildContext context, {Axis? axis}) {
|
||||
// This is the context that will need to establish the dependency.
|
||||
final BuildContext originalContext = context;
|
||||
final originalContext = context;
|
||||
InheritedElement? element = context
|
||||
.getElementForInheritedWidgetOfExactType<_ScrollableScope>();
|
||||
while (element != null) {
|
||||
@@ -477,7 +463,7 @@ class Scrollable<T extends HorizontalDragGestureRecognizer>
|
||||
ScrollPositionAlignmentPolicy alignmentPolicy =
|
||||
ScrollPositionAlignmentPolicy.explicit,
|
||||
}) {
|
||||
final List<Future<void>> futures = <Future<void>>[];
|
||||
final futures = <Future<void>>[];
|
||||
|
||||
// The targetRenderObject is used to record the first target renderObject.
|
||||
// If there are multiple scrollable widgets nested, the targetRenderObject
|
||||
@@ -855,7 +841,7 @@ class ScrollableState<T extends HorizontalDragGestureRecognizer>
|
||||
}
|
||||
_shouldIgnorePointer = value;
|
||||
if (_ignorePointerKey.currentContext != null) {
|
||||
final RenderIgnorePointer renderBox =
|
||||
final renderBox =
|
||||
_ignorePointerKey.currentContext!.findRenderObject()!
|
||||
as RenderIgnorePointer;
|
||||
renderBox.ignoring = _shouldIgnorePointer;
|
||||
@@ -1014,7 +1000,7 @@ class ScrollableState<T extends HorizontalDragGestureRecognizer>
|
||||
}
|
||||
|
||||
Widget _buildChrome(BuildContext context, Widget child) {
|
||||
final ScrollableDetails details = ScrollableDetails(
|
||||
final details = ScrollableDetails(
|
||||
direction: widget.axisDirection,
|
||||
controller: _effectiveScrollController,
|
||||
decorationClipBehavior: widget.clipBehavior,
|
||||
@@ -1344,7 +1330,7 @@ class _ScrollableSelectionContainerDelegate
|
||||
}
|
||||
|
||||
Offset _inferPositionRelatedToOrigin(Offset globalPosition) {
|
||||
final RenderBox box = state.context.findRenderObject()! as RenderBox;
|
||||
final box = state.context.findRenderObject()! as RenderBox;
|
||||
final Offset localPosition = box.globalToLocal(globalPosition);
|
||||
if (!_selectionStartsInScrollable) {
|
||||
// If the selection starts outside of the scrollable, selecting across the
|
||||
@@ -1377,7 +1363,7 @@ class _ScrollableSelectionContainerDelegate
|
||||
bool forceUpdateEnd = true,
|
||||
}) {
|
||||
final Offset deltaToOrigin = _getDeltaToScrollOrigin(state);
|
||||
final RenderBox box = state.context.findRenderObject()! as RenderBox;
|
||||
final box = state.context.findRenderObject()! as RenderBox;
|
||||
final Matrix4 transform = box.getTransformTo(null);
|
||||
if (currentSelectionStartIndex != -1 &&
|
||||
(_currentDragStartRelatedToOrigin == null || forceUpdateStart)) {
|
||||
@@ -1492,14 +1478,13 @@ class _ScrollableSelectionContainerDelegate
|
||||
if (lineHeight == null || edge == null) {
|
||||
return;
|
||||
}
|
||||
final RenderBox scrollableBox =
|
||||
state.context.findRenderObject()! as RenderBox;
|
||||
final scrollableBox = state.context.findRenderObject()! as RenderBox;
|
||||
final Matrix4 transform = selectable.getTransformTo(scrollableBox);
|
||||
final Offset edgeOffsetInScrollableCoordinates = MatrixUtils.transformPoint(
|
||||
transform,
|
||||
edge.localPosition,
|
||||
);
|
||||
final Rect scrollableRect = Rect.fromLTRB(
|
||||
final scrollableRect = Rect.fromLTRB(
|
||||
0,
|
||||
0,
|
||||
scrollableBox.size.width,
|
||||
@@ -1568,9 +1553,9 @@ class _ScrollableSelectionContainerDelegate
|
||||
}
|
||||
|
||||
bool _globalPositionInScrollable(Offset globalPosition) {
|
||||
final RenderBox box = state.context.findRenderObject()! as RenderBox;
|
||||
final box = state.context.findRenderObject()! as RenderBox;
|
||||
final Offset localPosition = box.globalToLocal(globalPosition);
|
||||
final Rect rect = Rect.fromLTRB(0, 0, box.size.width, box.size.height);
|
||||
final rect = Rect.fromLTRB(0, 0, box.size.width, box.size.height);
|
||||
return rect.contains(localPosition);
|
||||
}
|
||||
|
||||
@@ -1818,9 +1803,9 @@ class _RenderScrollSemantics extends RenderProxyBox {
|
||||
(_innerNode ??= SemanticsNode(showOnScreen: showOnScreen)).rect = node.rect;
|
||||
|
||||
int? firstVisibleIndex;
|
||||
final List<SemanticsNode> excluded = <SemanticsNode>[_innerNode!];
|
||||
final List<SemanticsNode> included = <SemanticsNode>[];
|
||||
for (final SemanticsNode child in children) {
|
||||
final excluded = <SemanticsNode>[_innerNode!];
|
||||
final included = <SemanticsNode>[];
|
||||
for (final child in children) {
|
||||
assert(child.isTagged(RenderViewport.useTwoPaneSemantics));
|
||||
if (child.isTagged(RenderViewport.excludeFromScrolling)) {
|
||||
excluded.add(child);
|
||||
|
||||
@@ -13,7 +13,8 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide ScrollableState;
|
||||
import 'package:flutter/material.dart'
|
||||
hide EdgeDraggingAutoScroller, Scrollable, ScrollableState;
|
||||
|
||||
/// An auto scroller that scrolls the [scrollable] if a drag gesture drags close
|
||||
/// to its edge.
|
||||
@@ -29,7 +30,7 @@ class EdgeDraggingAutoScroller {
|
||||
required this.velocityScalar,
|
||||
});
|
||||
|
||||
/// The [CustomScrollable] this auto scroller is scrolling.
|
||||
/// The [Scrollable] this auto scroller is scrolling.
|
||||
final ScrollableState scrollable;
|
||||
|
||||
/// Called when a scroll view is scrolled.
|
||||
@@ -97,8 +98,7 @@ class EdgeDraggingAutoScroller {
|
||||
}
|
||||
|
||||
Future<void> _scroll() async {
|
||||
final RenderBox scrollRenderBox =
|
||||
scrollable.context.findRenderObject()! as RenderBox;
|
||||
final scrollRenderBox = scrollable.context.findRenderObject()! as RenderBox;
|
||||
final Matrix4 transform = scrollRenderBox.getTransformTo(null);
|
||||
final Rect globalRect = MatrixUtils.transformRect(
|
||||
transform,
|
||||
@@ -123,7 +123,7 @@ class EdgeDraggingAutoScroller {
|
||||
);
|
||||
_scrolling = true;
|
||||
double? newOffset;
|
||||
const double overDragMax = 20.0;
|
||||
const overDragMax = 20.0;
|
||||
|
||||
final Offset deltaToOrigin = scrollable.deltaToScrollOrigin;
|
||||
final Offset viewportOrigin = globalRect.topLeft.translate(
|
||||
@@ -194,9 +194,7 @@ class EdgeDraggingAutoScroller {
|
||||
_scrolling = false;
|
||||
return;
|
||||
}
|
||||
final Duration duration = Duration(
|
||||
milliseconds: (1000 / velocityScalar).round(),
|
||||
);
|
||||
final duration = Duration(milliseconds: (1000 / velocityScalar).round());
|
||||
await scrollable.position.animateTo(
|
||||
newOffset,
|
||||
duration: duration,
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart';
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
import 'package:flutter/gestures.dart'
|
||||
show DragStartBehavior, HorizontalDragGestureRecognizer;
|
||||
import 'package:flutter/material.dart' hide PageView;
|
||||
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
||||
|
||||
/// A page view that displays the widget which corresponds to the currently
|
||||
/// selected tab.
|
||||
@@ -220,7 +220,7 @@ class _TabBarViewState<T extends HorizontalDragGestureRecognizer>
|
||||
return;
|
||||
}
|
||||
|
||||
final bool adjacentDestination =
|
||||
final adjacentDestination =
|
||||
(_currentIndex! - _controller!.previousIndex).abs() == 1;
|
||||
if (adjacentDestination) {
|
||||
_warpToAdjacentTab(_controller!.animationDuration);
|
||||
|
||||
@@ -149,7 +149,6 @@ class RefreshIndicator extends StatefulWidget {
|
||||
/// The [semanticsValue] may be used to specify progress on the widget.
|
||||
const RefreshIndicator({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.displacement = 40.0,
|
||||
this.edgeOffset = 0.0,
|
||||
required this.onRefresh,
|
||||
@@ -161,6 +160,7 @@ class RefreshIndicator extends StatefulWidget {
|
||||
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
|
||||
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
|
||||
this.elevation = 2.0,
|
||||
required this.child,
|
||||
}) : _indicatorType = _IndicatorType.material,
|
||||
onStatusChange = null,
|
||||
assert(elevation >= 0.0);
|
||||
@@ -183,7 +183,6 @@ class RefreshIndicator extends StatefulWidget {
|
||||
/// from [CupertinoSliverRefreshControl], due to a difference in structure.
|
||||
const RefreshIndicator.adaptive({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.displacement = 40.0,
|
||||
this.edgeOffset = 0.0,
|
||||
required this.onRefresh,
|
||||
@@ -195,6 +194,7 @@ class RefreshIndicator extends StatefulWidget {
|
||||
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
|
||||
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
|
||||
this.elevation = 2.0,
|
||||
required this.child,
|
||||
}) : _indicatorType = _IndicatorType.adaptive,
|
||||
onStatusChange = null,
|
||||
assert(elevation >= 0.0);
|
||||
@@ -205,7 +205,6 @@ class RefreshIndicator extends StatefulWidget {
|
||||
/// Events can be optionally listened by using the `onStatusChange` callback.
|
||||
const RefreshIndicator.noSpinner({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.onRefresh,
|
||||
this.onStatusChange,
|
||||
this.notificationPredicate = defaultScrollNotificationPredicate,
|
||||
@@ -213,6 +212,7 @@ class RefreshIndicator extends StatefulWidget {
|
||||
this.semanticsValue,
|
||||
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
|
||||
this.elevation = 2.0,
|
||||
required this.child,
|
||||
}) : _indicatorType = _IndicatorType.noSpinner,
|
||||
// The following parameters aren't used because [_IndicatorType.noSpinner] is being used,
|
||||
// which involves showing no spinner, hence the following parameters are useless since
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:flutter/gestures.dart'
|
||||
BaseTapAndDragGestureRecognizer,
|
||||
TapAndHorizontalDragGestureRecognizer,
|
||||
TapAndPanGestureRecognizer;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide SelectableRegion;
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -277,26 +277,28 @@ class SelectableRegion extends StatefulWidget {
|
||||
required final VoidCallback onSelectAll,
|
||||
required final VoidCallback? onShare,
|
||||
}) {
|
||||
final bool canCopy =
|
||||
selectionGeometry.status == SelectionStatus.uncollapsed;
|
||||
final canCopy = selectionGeometry.status == SelectionStatus.uncollapsed;
|
||||
final bool canSelectAll = selectionGeometry.hasContent;
|
||||
final bool platformCanShare = switch (defaultTargetPlatform) {
|
||||
TargetPlatform.android =>
|
||||
selectionGeometry.status == SelectionStatus.uncollapsed,
|
||||
TargetPlatform.macOS ||
|
||||
TargetPlatform.fuchsia ||
|
||||
TargetPlatform.linux ||
|
||||
TargetPlatform.windows => false,
|
||||
// TODO(bleroux): the share button should be shown on iOS but the share
|
||||
// functionality requires some changes on the engine side because, on iPad,
|
||||
// it needs an anchor for the popup.
|
||||
// See: https://github.com/flutter/flutter/issues/141775.
|
||||
TargetPlatform.iOS => false,
|
||||
};
|
||||
// The share button is not supported on the web.
|
||||
final bool platformCanShare =
|
||||
!kIsWeb &&
|
||||
switch (defaultTargetPlatform) {
|
||||
TargetPlatform.android =>
|
||||
selectionGeometry.status == SelectionStatus.uncollapsed,
|
||||
TargetPlatform.macOS ||
|
||||
TargetPlatform.fuchsia ||
|
||||
TargetPlatform.linux ||
|
||||
TargetPlatform.windows => false,
|
||||
// TODO(bleroux): the share button should be shown on iOS but the share
|
||||
// functionality requires some changes on the engine side because, on iPad,
|
||||
// it needs an anchor for the popup.
|
||||
// See: https://github.com/flutter/flutter/issues/141775.
|
||||
TargetPlatform.iOS => false,
|
||||
};
|
||||
final bool canShare = onShare != null && platformCanShare;
|
||||
|
||||
// On Android, the share button is before the select all button.
|
||||
final bool showShareBeforeSelectAll =
|
||||
final showShareBeforeSelectAll =
|
||||
defaultTargetPlatform == TargetPlatform.android;
|
||||
|
||||
// Determine which buttons will appear so that the order and total number is
|
||||
@@ -410,6 +412,16 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
Orientation? _lastOrientation;
|
||||
SelectedContent? _lastSelectedContent;
|
||||
|
||||
/// Whether the native browser context menu is enabled.
|
||||
// TODO(Renzo-Olivares): Re-enable web context menu for Android
|
||||
// and iOS when https://github.com/flutter/flutter/issues/177123
|
||||
// is resolved.
|
||||
bool get _webContextMenuEnabled =>
|
||||
kIsWeb &&
|
||||
BrowserContextMenu.enabled &&
|
||||
defaultTargetPlatform != TargetPlatform.android &&
|
||||
defaultTargetPlatform != TargetPlatform.iOS;
|
||||
|
||||
/// The [SelectionOverlay] that is currently visible on the screen.
|
||||
///
|
||||
/// Can be null if there is no visible [SelectionOverlay].
|
||||
@@ -513,7 +525,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
|
||||
void _handleFocusChanged() {
|
||||
if (!_focusNode.hasFocus) {
|
||||
if (kIsWeb) {
|
||||
if (_webContextMenuEnabled) {
|
||||
PlatformSelectableRegionContextMenu.detach(_selectionDelegate);
|
||||
}
|
||||
if (SchedulerBinding.instance.lifecycleState ==
|
||||
@@ -531,7 +543,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
_finalizeSelectableRegionStatus();
|
||||
}
|
||||
}
|
||||
if (kIsWeb) {
|
||||
if (_webContextMenuEnabled) {
|
||||
PlatformSelectableRegionContextMenu.attach(_selectionDelegate);
|
||||
}
|
||||
}
|
||||
@@ -596,7 +608,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
// This method should be used in all instances when details.consecutiveTapCount
|
||||
// would be used.
|
||||
int _getEffectiveConsecutiveTapCount(int rawCount) {
|
||||
int maxConsecutiveTap = 3;
|
||||
var maxConsecutiveTap = 3;
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
@@ -1177,21 +1189,9 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
_selectionOverlay != null &&
|
||||
(_selectionOverlay!.isDraggingStartHandle ||
|
||||
_selectionOverlay!.isDraggingEndHandle);
|
||||
if (widget.selectionControls is! TextSelectionHandleControls) {
|
||||
if (!draggingHandles) {
|
||||
_selectionOverlay!.hideMagnifier();
|
||||
_selectionOverlay!.showToolbar();
|
||||
}
|
||||
} else {
|
||||
if (!draggingHandles) {
|
||||
_selectionOverlay!.hideMagnifier();
|
||||
_selectionOverlay!.showToolbar(
|
||||
context: context,
|
||||
contextMenuBuilder: (BuildContext context) {
|
||||
return widget.contextMenuBuilder!(context, this);
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!draggingHandles) {
|
||||
_selectionOverlay!.hideMagnifier();
|
||||
_showToolbar();
|
||||
}
|
||||
_finalizeSelection();
|
||||
_updateSelectedContentIfNeeded();
|
||||
@@ -1335,13 +1335,13 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
final Vector3 globalTransform = _selectable!
|
||||
.getTransformTo(null)
|
||||
.getTranslation();
|
||||
final Offset globalTransformAsOffset = Offset(
|
||||
final globalTransformAsOffset = Offset(
|
||||
globalTransform.x,
|
||||
globalTransform.y,
|
||||
);
|
||||
final Offset globalSelectionPointPosition =
|
||||
selectionPoint.localPosition + globalTransformAsOffset;
|
||||
final Rect caretRect = Rect.fromLTWH(
|
||||
final caretRect = Rect.fromLTWH(
|
||||
globalSelectionPointPosition.dx,
|
||||
globalSelectionPointPosition.dy - selectionPoint.lineHeight,
|
||||
0,
|
||||
@@ -1439,7 +1439,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
// functionality depending on the browser (such as translate). Due to this,
|
||||
// we should not show a Flutter toolbar for the editable text elements
|
||||
// unless the browser's context menu is explicitly disabled.
|
||||
if (kIsWeb && BrowserContextMenu.enabled) {
|
||||
if (_webContextMenuEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1448,6 +1448,9 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
}
|
||||
|
||||
_selectionOverlay!.toolbarLocation = location;
|
||||
// TODO(Renzo-Olivares): Remove the logic below that does a runtimeType
|
||||
// check for TextSelectionHandleControls when TextSelectionHandleControls
|
||||
// is fully removed, see: https://github.com/flutter/flutter/pull/124262.
|
||||
if (widget.selectionControls is! TextSelectionHandleControls) {
|
||||
_selectionOverlay!.showToolbar();
|
||||
return true;
|
||||
@@ -1683,7 +1686,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
/// for the default context menu buttons.
|
||||
TextSelectionToolbarAnchors get contextMenuAnchors {
|
||||
if (_lastSecondaryTapDownPosition != null) {
|
||||
final TextSelectionToolbarAnchors anchors = TextSelectionToolbarAnchors(
|
||||
final anchors = TextSelectionToolbarAnchors(
|
||||
primaryAnchor: _lastSecondaryTapDownPosition!,
|
||||
);
|
||||
// Clear the state of _lastSecondaryTapDownPosition after use since a user may
|
||||
@@ -1691,7 +1694,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
_lastSecondaryTapDownPosition = null;
|
||||
return anchors;
|
||||
}
|
||||
final RenderBox renderBox = context.findRenderObject()! as RenderBox;
|
||||
final renderBox = context.findRenderObject()! as RenderBox;
|
||||
return TextSelectionToolbarAnchors.fromSelection(
|
||||
renderBox: renderBox,
|
||||
startGlyphHeight: startGlyphHeight,
|
||||
@@ -1843,7 +1846,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
}
|
||||
|
||||
List<ContextMenuButtonItem> get _textProcessingActionButtonItems {
|
||||
final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
|
||||
final buttonItems = <ContextMenuButtonItem>[];
|
||||
final SelectedContent? data = _selectable?.getSelectedContent();
|
||||
if (data == null) {
|
||||
return buttonItems;
|
||||
@@ -2048,7 +2051,7 @@ class SelectableRegionState extends State<SelectableRegion>
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
if (kIsWeb) {
|
||||
if (_webContextMenuEnabled) {
|
||||
result = PlatformSelectableRegionContextMenu(child: result);
|
||||
}
|
||||
return CompositedTransformTarget(
|
||||
|
||||
@@ -8,7 +8,8 @@ import 'package:PiliPlus/common/widgets/flutter/selectable_text/text_selection.d
|
||||
import 'package:flutter/cupertino.dart'
|
||||
hide TextSelectionGestureDetectorBuilder;
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart' hide TextSelectionGestureDetectorBuilder;
|
||||
import 'package:flutter/material.dart'
|
||||
hide SelectableText, TextSelectionGestureDetectorBuilder;
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
@@ -610,8 +611,9 @@ class _SelectableTextState extends State<SelectableText>
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.data != oldWidget.data ||
|
||||
widget.textSpan != oldWidget.textSpan) {
|
||||
_controller.removeListener(_onControllerChanged);
|
||||
_controller.dispose();
|
||||
_controller
|
||||
..removeListener(_onControllerChanged)
|
||||
..dispose();
|
||||
_controller = _TextSpanEditingController(
|
||||
textSpan: widget.textSpan ?? TextSpan(text: widget.data),
|
||||
);
|
||||
@@ -766,7 +768,7 @@ class _SelectableTextState extends State<SelectableText>
|
||||
cupertinoTheme.primaryColor;
|
||||
selectionColor =
|
||||
selectionStyle.selectionColor ??
|
||||
cupertinoTheme.primaryColor.withOpacity(0.40);
|
||||
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
|
||||
cursorRadius ??= const Radius.circular(2.0);
|
||||
cursorOffset = Offset(
|
||||
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
|
||||
@@ -785,7 +787,7 @@ class _SelectableTextState extends State<SelectableText>
|
||||
cupertinoTheme.primaryColor;
|
||||
selectionColor =
|
||||
selectionStyle.selectionColor ??
|
||||
cupertinoTheme.primaryColor.withOpacity(0.40);
|
||||
cupertinoTheme.primaryColor.withValues(alpha: 0.40);
|
||||
cursorRadius ??= const Radius.circular(2.0);
|
||||
cursorOffset = Offset(
|
||||
iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context),
|
||||
@@ -804,7 +806,7 @@ class _SelectableTextState extends State<SelectableText>
|
||||
theme.colorScheme.primary;
|
||||
selectionColor =
|
||||
selectionStyle.selectionColor ??
|
||||
theme.colorScheme.primary.withOpacity(0.40);
|
||||
theme.colorScheme.primary.withValues(alpha: 0.40);
|
||||
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
@@ -818,7 +820,7 @@ class _SelectableTextState extends State<SelectableText>
|
||||
theme.colorScheme.primary;
|
||||
selectionColor =
|
||||
selectionStyle.selectionColor ??
|
||||
theme.colorScheme.primary.withOpacity(0.40);
|
||||
theme.colorScheme.primary.withValues(alpha: 0.40);
|
||||
}
|
||||
|
||||
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter/cupertino.dart'
|
||||
SelectableRegionContextMenuBuilder;
|
||||
import 'package:flutter/material.dart'
|
||||
hide
|
||||
SelectionArea,
|
||||
SelectableRegion,
|
||||
SelectableRegionState,
|
||||
SelectableRegionContextMenuBuilder;
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/gestures.dart'
|
||||
hide TapAndHorizontalDragGestureRecognizer;
|
||||
|
||||
// Examples can assume:
|
||||
// void setState(VoidCallback fn) { }
|
||||
@@ -823,7 +824,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
untransformedDelta: localDelta,
|
||||
untransformedEndPosition: correctedLocalPosition,
|
||||
);
|
||||
final OffsetPair updateDelta = OffsetPair(
|
||||
final updateDelta = OffsetPair(
|
||||
local: localDelta,
|
||||
global: globalUpdateDelta,
|
||||
);
|
||||
@@ -870,7 +871,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
return;
|
||||
}
|
||||
|
||||
final TapDragDownDetails details = TapDragDownDetails(
|
||||
final details = TapDragDownDetails(
|
||||
globalPosition: event.position,
|
||||
localPosition: event.localPosition,
|
||||
kind: getKindForPointer(event.pointer),
|
||||
@@ -889,7 +890,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
return;
|
||||
}
|
||||
|
||||
final TapDragUpDetails upDetails = TapDragUpDetails(
|
||||
final upDetails = TapDragUpDetails(
|
||||
kind: event.kind,
|
||||
globalPosition: event.position,
|
||||
localPosition: event.localPosition,
|
||||
@@ -908,7 +909,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
|
||||
void _checkDragStart(PointerEvent event) {
|
||||
if (onDragStart != null) {
|
||||
final TapDragStartDetails details = TapDragStartDetails(
|
||||
final details = TapDragStartDetails(
|
||||
sourceTimeStamp: event.timeStamp,
|
||||
globalPosition: _initialPosition.global,
|
||||
localPosition: _initialPosition.local,
|
||||
@@ -926,7 +927,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
final Offset globalPosition = corrected?.global ?? event.position;
|
||||
final Offset localPosition = corrected?.local ?? event.localPosition;
|
||||
|
||||
final TapDragUpdateDetails details = TapDragUpdateDetails(
|
||||
final details = TapDragUpdateDetails(
|
||||
sourceTimeStamp: event.timeStamp,
|
||||
delta: event.localDelta,
|
||||
globalPosition: globalPosition,
|
||||
@@ -962,7 +963,7 @@ sealed class BaseTapAndDragGestureRecognizer
|
||||
_handleDragUpdateThrottled();
|
||||
}
|
||||
|
||||
final TapDragEndDetails endDetails = TapDragEndDetails(
|
||||
final endDetails = TapDragEndDetails(
|
||||
globalPosition: globalPosition,
|
||||
localPosition: localPosition,
|
||||
primaryVelocity: 0.0,
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:flutter/gestures.dart'
|
||||
BaseTapAndDragGestureRecognizer,
|
||||
TapAndHorizontalDragGestureRecognizer,
|
||||
TapAndPanGestureRecognizer;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide TextSelectionGestureDetector;
|
||||
|
||||
class CustomTextSelectionGestureDetectorBuilder
|
||||
extends TextSelectionGestureDetectorBuilder {
|
||||
@@ -310,8 +310,7 @@ class _TextSelectionGestureDetectorState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Map<Type, GestureRecognizerFactory> gestures =
|
||||
<Type, GestureRecognizerFactory>{};
|
||||
final gestures = <Type, GestureRecognizerFactory>{};
|
||||
|
||||
gestures[TapGestureRecognizer] =
|
||||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||||
|
||||
@@ -24,7 +24,7 @@ import 'dart:ui'
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// The start and end positions for a text boundary.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import 'dart:ui' as ui show TextHeightBehavior;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide RichText;
|
||||
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
||||
|
||||
/// A paragraph of rich text.
|
||||
@@ -118,8 +118,8 @@ class RichText extends MultiChildRenderObjectWidget {
|
||||
this.textHeightBehavior,
|
||||
this.selectionRegistrar,
|
||||
this.selectionColor,
|
||||
this.onShowMore,
|
||||
required this.primary,
|
||||
this.onShowMore,
|
||||
}) : assert(maxLines == null || maxLines > 0),
|
||||
assert(selectionRegistrar == null || selectionColor != null),
|
||||
assert(
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'dart:ui' as ui show TextHeightBehavior;
|
||||
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/text/rich_text.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart' hide RichText;
|
||||
import 'package:flutter/material.dart' hide Text, RichText;
|
||||
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
||||
|
||||
/// A run of text with a single style.
|
||||
@@ -180,8 +180,8 @@ class Text extends StatelessWidget {
|
||||
this.textWidthBasis,
|
||||
this.textHeightBehavior,
|
||||
this.selectionColor,
|
||||
this.onShowMore,
|
||||
required this.primary,
|
||||
this.onShowMore,
|
||||
}) : textSpan = null,
|
||||
assert(
|
||||
textScaler == null || textScaleFactor == null,
|
||||
@@ -219,8 +219,8 @@ class Text extends StatelessWidget {
|
||||
this.textWidthBasis,
|
||||
this.textHeightBehavior,
|
||||
this.selectionColor,
|
||||
this.onShowMore,
|
||||
required this.primary,
|
||||
this.onShowMore,
|
||||
}) : data = null,
|
||||
assert(
|
||||
textScaler == null || textScaleFactor == null,
|
||||
@@ -242,9 +242,19 @@ class Text extends StatelessWidget {
|
||||
/// If the style's "inherit" property is true, the style will be merged with
|
||||
/// the closest enclosing [DefaultTextStyle]. Otherwise, the style will
|
||||
/// replace the closest enclosing [DefaultTextStyle].
|
||||
///
|
||||
/// 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;
|
||||
|
||||
/// {@macro flutter.painting.textPainter.strutStyle}
|
||||
///
|
||||
/// The user or platform may override this [strutStyle]'s [StrutStyle.height]
|
||||
/// via a [MediaQuery] ancestor's [MediaQueryData.lineHeightScaleFactorOverride].
|
||||
final StrutStyle? strutStyle;
|
||||
|
||||
/// How the text should be aligned horizontally.
|
||||
@@ -375,6 +385,30 @@ class Text extends StatelessWidget {
|
||||
const TextStyle(fontWeight: FontWeight.bold),
|
||||
);
|
||||
}
|
||||
// TODO(Renzo-Olivares): Investigate ways the framework can automatically
|
||||
// apply MediaQueryData.paragraphSpacingOverride to its own text components.
|
||||
// See: https://github.com/flutter/flutter/issues/177953 and https://github.com/flutter/flutter/issues/177408.
|
||||
final double? lineHeightScaleFactor =
|
||||
MediaQuery.maybeLineHeightScaleFactorOverrideOf(context);
|
||||
final double? letterSpacing = MediaQuery.maybeLetterSpacingOverrideOf(
|
||||
context,
|
||||
);
|
||||
final double? wordSpacing = MediaQuery.maybeWordSpacingOverrideOf(context);
|
||||
final TextSpan effectiveTextSpan =
|
||||
_OverridingTextStyleTextSpanUtils.applyTextSpacingOverrides(
|
||||
lineHeightScaleFactor: lineHeightScaleFactor,
|
||||
letterSpacing: letterSpacing,
|
||||
wordSpacing: wordSpacing,
|
||||
textSpan: TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: data,
|
||||
locale: locale,
|
||||
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
||||
),
|
||||
);
|
||||
final StrutStyle? effectiveStrutStyle = strutStyle?.merge(
|
||||
StrutStyle(height: lineHeightScaleFactor),
|
||||
);
|
||||
final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
|
||||
final TextScaler textScaler = switch ((this.textScaler, textScaleFactor)) {
|
||||
(final TextScaler textScaler, _) => textScaler,
|
||||
@@ -403,7 +437,7 @@ class Text extends StatelessWidget {
|
||||
defaultTextStyle.overflow,
|
||||
textScaler: textScaler,
|
||||
maxLines: maxLines ?? defaultTextStyle.maxLines,
|
||||
strutStyle: strutStyle,
|
||||
strutStyle: effectiveStrutStyle,
|
||||
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
|
||||
textHeightBehavior:
|
||||
textHeightBehavior ??
|
||||
@@ -413,12 +447,7 @@ class Text extends StatelessWidget {
|
||||
selectionColor ??
|
||||
DefaultSelectionStyle.of(context).selectionColor ??
|
||||
DefaultSelectionStyle.defaultColor,
|
||||
text: TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: data,
|
||||
locale: locale,
|
||||
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
||||
),
|
||||
text: effectiveTextSpan,
|
||||
primary: primary,
|
||||
),
|
||||
);
|
||||
@@ -436,7 +465,7 @@ class Text extends StatelessWidget {
|
||||
defaultTextStyle.overflow,
|
||||
textScaler: textScaler,
|
||||
maxLines: maxLines ?? defaultTextStyle.maxLines,
|
||||
strutStyle: strutStyle,
|
||||
strutStyle: effectiveStrutStyle,
|
||||
textWidthBasis: textWidthBasis ?? defaultTextStyle.textWidthBasis,
|
||||
textHeightBehavior:
|
||||
textHeightBehavior ??
|
||||
@@ -446,14 +475,9 @@ class Text extends StatelessWidget {
|
||||
selectionColor ??
|
||||
DefaultSelectionStyle.of(context).selectionColor ??
|
||||
DefaultSelectionStyle.defaultColor,
|
||||
text: TextSpan(
|
||||
style: effectiveTextStyle,
|
||||
text: data,
|
||||
locale: locale,
|
||||
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
||||
),
|
||||
onShowMore: onShowMore,
|
||||
text: effectiveTextSpan,
|
||||
primary: primary,
|
||||
onShowMore: onShowMore,
|
||||
);
|
||||
}
|
||||
if (semanticsLabel != null || semanticsIdentifier != null) {
|
||||
@@ -483,49 +507,50 @@ class Text extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
style?.debugFillProperties(properties);
|
||||
properties.add(
|
||||
EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null),
|
||||
);
|
||||
properties.add(
|
||||
EnumProperty<TextDirection>(
|
||||
'textDirection',
|
||||
textDirection,
|
||||
defaultValue: null,
|
||||
),
|
||||
);
|
||||
properties.add(
|
||||
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
||||
);
|
||||
properties.add(
|
||||
FlagProperty(
|
||||
'softWrap',
|
||||
value: softWrap,
|
||||
ifTrue: 'wrapping at box width',
|
||||
ifFalse: 'no wrapping except at line break characters',
|
||||
showName: true,
|
||||
),
|
||||
);
|
||||
properties.add(
|
||||
EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null),
|
||||
);
|
||||
properties.add(
|
||||
DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null),
|
||||
);
|
||||
properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
|
||||
properties.add(
|
||||
EnumProperty<TextWidthBasis>(
|
||||
'textWidthBasis',
|
||||
textWidthBasis,
|
||||
defaultValue: null,
|
||||
),
|
||||
);
|
||||
properties.add(
|
||||
DiagnosticsProperty<ui.TextHeightBehavior>(
|
||||
'textHeightBehavior',
|
||||
textHeightBehavior,
|
||||
defaultValue: null,
|
||||
),
|
||||
);
|
||||
properties
|
||||
..add(
|
||||
EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null),
|
||||
)
|
||||
..add(
|
||||
EnumProperty<TextDirection>(
|
||||
'textDirection',
|
||||
textDirection,
|
||||
defaultValue: null,
|
||||
),
|
||||
)
|
||||
..add(
|
||||
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
||||
)
|
||||
..add(
|
||||
FlagProperty(
|
||||
'softWrap',
|
||||
value: softWrap,
|
||||
ifTrue: 'wrapping at box width',
|
||||
ifFalse: 'no wrapping except at line break characters',
|
||||
showName: true,
|
||||
),
|
||||
)
|
||||
..add(
|
||||
EnumProperty<TextOverflow>('overflow', overflow, defaultValue: null),
|
||||
)
|
||||
..add(
|
||||
DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null),
|
||||
)
|
||||
..add(IntProperty('maxLines', maxLines, defaultValue: null))
|
||||
..add(
|
||||
EnumProperty<TextWidthBasis>(
|
||||
'textWidthBasis',
|
||||
textWidthBasis,
|
||||
defaultValue: null,
|
||||
),
|
||||
)
|
||||
..add(
|
||||
DiagnosticsProperty<ui.TextHeightBehavior>(
|
||||
'textHeightBehavior',
|
||||
textHeightBehavior,
|
||||
defaultValue: null,
|
||||
),
|
||||
);
|
||||
if (semanticsLabel != null) {
|
||||
properties.add(StringProperty('semanticsLabel', semanticsLabel));
|
||||
}
|
||||
@@ -693,7 +718,7 @@ class _SelectableTextContainerDelegate
|
||||
|
||||
SelectionResult _handleSelectParagraph(SelectParagraphSelectionEvent event) {
|
||||
if (event.absorb) {
|
||||
for (int index = 0; index < selectables.length; index += 1) {
|
||||
for (var index = 0; index < selectables.length; index += 1) {
|
||||
dispatchSelectionEventToChild(selectables[index], event);
|
||||
}
|
||||
currentSelectionStartIndex = 0;
|
||||
@@ -703,7 +728,7 @@ class _SelectableTextContainerDelegate
|
||||
|
||||
// First pass, if the position is on a placeholder then dispatch the selection
|
||||
// event to the [Selectable] at the location and terminate.
|
||||
for (int index = 0; index < selectables.length; index += 1) {
|
||||
for (var index = 0; index < selectables.length; index += 1) {
|
||||
final bool selectableIsPlaceholder = !paragraph
|
||||
.selectableBelongsToParagraph(selectables[index]);
|
||||
if (selectableIsPlaceholder &&
|
||||
@@ -722,9 +747,9 @@ class _SelectableTextContainerDelegate
|
||||
}
|
||||
|
||||
SelectionResult? lastSelectionResult;
|
||||
bool foundStart = false;
|
||||
var foundStart = false;
|
||||
int? lastNextIndex;
|
||||
for (int index = 0; index < selectables.length; index += 1) {
|
||||
for (var index = 0; index < selectables.length; index += 1) {
|
||||
if (!paragraph.selectableBelongsToParagraph(selectables[index])) {
|
||||
if (foundStart) {
|
||||
final SelectionEvent synthesizedEvent = SelectParagraphSelectionEvent(
|
||||
@@ -769,7 +794,7 @@ class _SelectableTextContainerDelegate
|
||||
.overlaps(
|
||||
selectables[index].value.selectionRects[0],
|
||||
);
|
||||
int startIndex = 0;
|
||||
var startIndex = 0;
|
||||
if (lastNextIndex != null && selectionAtStartOfSelectable) {
|
||||
startIndex = lastNextIndex + 1;
|
||||
} else {
|
||||
@@ -777,7 +802,7 @@ class _SelectableTextContainerDelegate
|
||||
? 0
|
||||
: index;
|
||||
}
|
||||
for (int i = startIndex; i < index; i += 1) {
|
||||
for (var i = startIndex; i < index; i += 1) {
|
||||
final SelectionEvent synthesizedEvent =
|
||||
SelectParagraphSelectionEvent(
|
||||
globalPosition: event.globalPosition,
|
||||
@@ -796,7 +821,7 @@ class _SelectableTextContainerDelegate
|
||||
if (selectables[index].value != existingGeometry) {
|
||||
if (!foundStart && lastNextIndex == null) {
|
||||
currentSelectionStartIndex = 0;
|
||||
for (int i = 0; i < index; i += 1) {
|
||||
for (var i = 0; i < index; i += 1) {
|
||||
final SelectionEvent synthesizedEvent =
|
||||
SelectParagraphSelectionEvent(
|
||||
globalPosition: event.globalPosition,
|
||||
@@ -837,7 +862,7 @@ class _SelectableTextContainerDelegate
|
||||
);
|
||||
SelectionResult? finalResult;
|
||||
// Begin the search for the selection edge at the opposite edge if it exists.
|
||||
final bool hasOppositeEdge = isEnd
|
||||
final hasOppositeEdge = isEnd
|
||||
? currentSelectionStartIndex != -1
|
||||
: currentSelectionEndIndex != -1;
|
||||
int newIndex = switch ((isEnd, hasOppositeEdge)) {
|
||||
@@ -932,10 +957,10 @@ class _SelectableTextContainerDelegate
|
||||
//
|
||||
// This can happen when there is a scrollable child and the edge being adjusted
|
||||
// has been scrolled out of view.
|
||||
final bool isCurrentEdgeWithinViewport = isEnd
|
||||
final isCurrentEdgeWithinViewport = isEnd
|
||||
? value.endSelectionPoint != null
|
||||
: value.startSelectionPoint != null;
|
||||
final bool isOppositeEdgeWithinViewport = isEnd
|
||||
final isOppositeEdgeWithinViewport = isEnd
|
||||
? value.startSelectionPoint != null
|
||||
: value.endSelectionPoint != null;
|
||||
int newIndex = switch ((
|
||||
@@ -1107,9 +1132,9 @@ class _SelectableTextContainerDelegate
|
||||
if (currentSelectionStartIndex == -1 || currentSelectionEndIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
int startOffset = 0;
|
||||
int endOffset = 0;
|
||||
bool foundStart = false;
|
||||
var startOffset = 0;
|
||||
var endOffset = 0;
|
||||
var foundStart = false;
|
||||
bool forwardSelection =
|
||||
currentSelectionEndIndex >= currentSelectionStartIndex;
|
||||
if (currentSelectionEndIndex == currentSelectionStartIndex) {
|
||||
@@ -1121,7 +1146,7 @@ class _SelectableTextContainerDelegate
|
||||
rangeAtSelectableInSelection.endOffset >=
|
||||
rangeAtSelectableInSelection.startOffset;
|
||||
}
|
||||
for (int index = 0; index < selections.length; index++) {
|
||||
for (var index = 0; index < selections.length; index++) {
|
||||
final _SelectionInfo selection = selections[index];
|
||||
if (selection.range == null) {
|
||||
if (foundStart) {
|
||||
@@ -1187,7 +1212,7 @@ class _SelectableTextContainerDelegate
|
||||
/// this method will return `null`.
|
||||
@override
|
||||
SelectedContentRange? getSelection() {
|
||||
final List<_SelectionInfo> selections = <_SelectionInfo>[
|
||||
final selections = <_SelectionInfo>[
|
||||
for (final Selectable selectable in selectables)
|
||||
(
|
||||
contentLength: selectable.contentLength,
|
||||
@@ -1232,7 +1257,7 @@ class _SelectableTextContainerDelegate
|
||||
currentSelectionStartIndex,
|
||||
currentSelectionEndIndex,
|
||||
);
|
||||
for (int index = 0; index < selectables.length; index += 1) {
|
||||
for (var index = 0; index < selectables.length; index += 1) {
|
||||
if (index >= skipStart && index <= skipEnd) {
|
||||
continue;
|
||||
}
|
||||
@@ -1266,3 +1291,55 @@ class _SelectableTextContainerDelegate
|
||||
/// The length of the content that can be selected, and the range that is
|
||||
/// selected.
|
||||
typedef _SelectionInfo = ({int contentLength, SelectedContentRange? range});
|
||||
|
||||
/// A utility class for overriding the text styles of a [TextSpan] tree.
|
||||
// When changes are made to this class, the equivalent API in editable_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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1571,7 +1571,6 @@ class _VerticalTabBarState extends State<VerticalTabBar> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
assert(debugCheckHasMaterial(context));
|
||||
_updateTabController();
|
||||
_initIndicatorPainter();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user