mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add Overlay.maybeOf
, make Overlay.of
return a non-nullable instance (#110811)
This commit is contained in:
parent
a7ba717b2f
commit
2051f09c68
@ -132,7 +132,7 @@ class _OverlayExampleState extends State<OverlayExample> {
|
||||
);
|
||||
|
||||
// Add the OverlayEntry to the Overlay.
|
||||
Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!);
|
||||
Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);
|
||||
}
|
||||
|
||||
// Remove the OverlayEntry.
|
||||
|
@ -363,7 +363,7 @@ class _CupertinoContextMenuState extends State<CupertinoContextMenu> with Ticker
|
||||
);
|
||||
},
|
||||
);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: widget)!.insert(_lastOverlayEntry!);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: widget).insert(_lastOverlayEntry!);
|
||||
_openController.forward();
|
||||
}
|
||||
|
||||
|
@ -655,7 +655,7 @@ class _RangeSliderState extends State<RangeSlider> with TickerProviderStateMixin
|
||||
);
|
||||
},
|
||||
);
|
||||
Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!);
|
||||
Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -887,7 +887,7 @@ class _SliderState extends State<Slider> with TickerProviderStateMixin {
|
||||
);
|
||||
},
|
||||
);
|
||||
Overlay.of(context, debugRequiredFor: widget)!.insert(overlayEntry!);
|
||||
Overlay.of(context, debugRequiredFor: widget).insert(overlayEntry!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -517,7 +517,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
||||
final OverlayState overlayState = Overlay.of(
|
||||
context,
|
||||
debugRequiredFor: widget,
|
||||
)!;
|
||||
);
|
||||
overlayState.insert(_entry!);
|
||||
}
|
||||
SemanticsService.tooltip(_tooltipMessage);
|
||||
@ -573,7 +573,7 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
||||
final OverlayState overlayState = Overlay.of(
|
||||
context,
|
||||
debugRequiredFor: widget,
|
||||
)!;
|
||||
);
|
||||
|
||||
final RenderBox box = context.findRenderObject()! as RenderBox;
|
||||
final Offset target = box.localToGlobal(
|
||||
|
@ -438,7 +438,7 @@ class _RawAutocompleteState<T extends Object> extends State<RawAutocomplete<T>>
|
||||
);
|
||||
},
|
||||
);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: widget)!.insert(newFloatingOptions);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: widget).insert(newFloatingOptions);
|
||||
_floatingOptions = newFloatingOptions;
|
||||
} else {
|
||||
_floatingOptions = null;
|
||||
|
@ -555,7 +555,7 @@ class _DraggableState<T extends Object> extends State<Draggable<T>> {
|
||||
_activeCount += 1;
|
||||
});
|
||||
final _DragAvatar<T> avatar = _DragAvatar<T>(
|
||||
overlayState: Overlay.of(context, debugRequiredFor: widget, rootOverlay: widget.rootOverlay)!,
|
||||
overlayState: Overlay.of(context, debugRequiredFor: widget, rootOverlay: widget.rootOverlay),
|
||||
data: widget.data,
|
||||
axis: widget.axis,
|
||||
initialPosition: position,
|
||||
|
@ -168,7 +168,7 @@ class MagnifierController {
|
||||
/// final MagnifierController myMagnifierController = MagnifierController();
|
||||
///
|
||||
/// // Placed below the magnifier, so it will show.
|
||||
/// Overlay.of(context)!.insert(OverlayEntry(
|
||||
/// Overlay.of(context).insert(OverlayEntry(
|
||||
/// builder: (BuildContext context) => const Text('I WILL display in the magnifier')));
|
||||
///
|
||||
/// // Will display in the magnifier, since this entry was passed to show.
|
||||
@ -176,7 +176,7 @@ class MagnifierController {
|
||||
/// builder: (BuildContext context) =>
|
||||
/// const Text('I WILL display in the magnifier'));
|
||||
///
|
||||
/// Overlay.of(context)!
|
||||
/// Overlay.of(context)
|
||||
/// .insert(displayInMagnifier);
|
||||
/// myMagnifierController.show(
|
||||
/// context: context,
|
||||
@ -186,10 +186,10 @@ class MagnifierController {
|
||||
/// ));
|
||||
///
|
||||
/// // By default, new entries will be placed over the top entry.
|
||||
/// Overlay.of(context)!.insert(OverlayEntry(
|
||||
/// Overlay.of(context).insert(OverlayEntry(
|
||||
/// builder: (BuildContext context) => const Text('I WILL NOT display in the magnifier')));
|
||||
///
|
||||
/// Overlay.of(context)!.insert(
|
||||
/// Overlay.of(context).insert(
|
||||
/// below:
|
||||
/// myMagnifierController.overlayEntry, // Explicitly placed below the magnifier.
|
||||
/// OverlayEntry(
|
||||
@ -246,7 +246,7 @@ class MagnifierController {
|
||||
overlayEntry!.remove();
|
||||
}
|
||||
|
||||
final OverlayState? overlayState = Overlay.of(
|
||||
final OverlayState overlayState = Overlay.of(
|
||||
context,
|
||||
rootOverlay: true,
|
||||
debugRequiredFor: debugRequiredFor,
|
||||
@ -260,7 +260,7 @@ class MagnifierController {
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (BuildContext context) => capturedThemes.wrap(builder(context)),
|
||||
);
|
||||
overlayState!.insert(overlayEntry!, below: below);
|
||||
overlayState.insert(overlayEntry!, below: below);
|
||||
|
||||
if (animationController != null) {
|
||||
await animationController?.forward();
|
||||
@ -389,7 +389,7 @@ class MagnifierDecoration extends ShapeDecoration {
|
||||
/// gesture, on an image or text.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// A magnifier can be convienently managed by [MagnifierController], which handles
|
||||
/// A magnifier can be conveniently managed by [MagnifierController], which handles
|
||||
/// showing and hiding the magnifier, with an optional entry / exit animation.
|
||||
///
|
||||
/// See:
|
||||
@ -402,7 +402,7 @@ class RawMagnifier extends StatelessWidget {
|
||||
/// the focal point is directly under the magnifier, and there is no magnification:
|
||||
/// This means that a default magnifier will be entirely invisible to the naked eye,
|
||||
/// since it is painting exactly what is under it, exactly where it was painted
|
||||
/// orignally.
|
||||
/// originally.
|
||||
/// {@endtemplate}
|
||||
const RawMagnifier({
|
||||
super.key,
|
||||
@ -414,7 +414,7 @@ class RawMagnifier extends StatelessWidget {
|
||||
}) : assert(magnificationScale != 0,
|
||||
'Magnification scale of 0 results in undefined behavior.');
|
||||
|
||||
/// An optional widget to posiiton inside the len of the [RawMagnifier].
|
||||
/// An optional widget to position inside the len of the [RawMagnifier].
|
||||
///
|
||||
/// This is positioned over the [RawMagnifier] - it may be useful for tinting the
|
||||
/// [RawMagnifier], or drawing a crosshair like UI.
|
||||
|
@ -273,8 +273,9 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
|
||||
/// [OverlayEntry] objects.
|
||||
///
|
||||
/// Although you can create an [Overlay] directly, it's most common to use the
|
||||
/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The
|
||||
/// navigator uses its overlay to manage the visual appearance of its routes.
|
||||
/// overlay created by the [Navigator] in a [WidgetsApp], [CupertinoApp] or a
|
||||
/// [MaterialApp]. The navigator uses its overlay to manage the visual
|
||||
/// appearance of its routes.
|
||||
///
|
||||
/// The [Overlay] widget uses a custom stack implementation, which is very
|
||||
/// similar to the [Stack] widget. The main use case of [Overlay] is related to
|
||||
@ -298,6 +299,7 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
|
||||
/// * [OverlayState], which is used to insert the entries into the overlay.
|
||||
/// * [WidgetsApp], which inserts an [Overlay] widget indirectly via its [Navigator].
|
||||
/// * [MaterialApp], which inserts an [Overlay] widget indirectly via its [Navigator].
|
||||
/// * [CupertinoApp], which inserts an [Overlay] widget indirectly via its [Navigator].
|
||||
/// * [Stack], which allows directly displaying a stack of widgets.
|
||||
class Overlay extends StatefulWidget {
|
||||
/// Creates an overlay.
|
||||
@ -306,7 +308,8 @@ class Overlay extends StatefulWidget {
|
||||
/// [OverlayState] is initialized.
|
||||
///
|
||||
/// Rather than creating an overlay, consider using the overlay that is
|
||||
/// created by the [Navigator] in a [WidgetsApp] or a [MaterialApp] for the application.
|
||||
/// created by the [Navigator] in a [WidgetsApp], [CupertinoApp], or a
|
||||
/// [MaterialApp] for the application.
|
||||
const Overlay({
|
||||
super.key,
|
||||
this.initialEntries = const <OverlayEntry>[],
|
||||
@ -334,40 +337,46 @@ class Overlay extends StatefulWidget {
|
||||
/// Defaults to [Clip.hardEdge], and must not be null.
|
||||
final Clip clipBehavior;
|
||||
|
||||
/// The state from the closest instance of this class that encloses the given context.
|
||||
/// The [OverlayState] from the closest instance of [Overlay] that encloses
|
||||
/// the given context, and, in debug mode, will throw if one is not found.
|
||||
///
|
||||
/// In debug mode, if the `debugRequiredFor` argument is provided then this
|
||||
/// function will assert that an overlay was found and will throw an exception
|
||||
/// if not. The exception attempts to explain that the calling [Widget] (the
|
||||
/// one given by the `debugRequiredFor` argument) needs an [Overlay] to be
|
||||
/// present to function.
|
||||
/// In debug mode, if the `debugRequiredFor` argument is provided and an
|
||||
/// overlay isn't found, then this function will throw an exception containing
|
||||
/// the runtime type of the given widget in the error message. The exception
|
||||
/// attempts to explain that the calling [Widget] (the one given by the
|
||||
/// `debugRequiredFor` argument) needs an [Overlay] to be present to function.
|
||||
/// If `debugRequiredFor` is not supplied, then the error message is more
|
||||
/// generic.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// OverlayState overlay = Overlay.of(context)!;
|
||||
/// OverlayState overlay = Overlay.of(context);
|
||||
/// ```
|
||||
///
|
||||
/// If `rootOverlay` is set to true, the state from the furthest instance of
|
||||
/// this class is given instead. Useful for installing overlay entries
|
||||
/// above all subsequent instances of [Overlay].
|
||||
/// this class is given instead. Useful for installing overlay entries above
|
||||
/// all subsequent instances of [Overlay].
|
||||
///
|
||||
/// This method can be expensive (it walks the element tree).
|
||||
static OverlayState? of(
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Overlay.maybeOf] for a similar function that returns null if an
|
||||
/// [Overlay] is not found.
|
||||
static OverlayState of(
|
||||
BuildContext context, {
|
||||
bool rootOverlay = false,
|
||||
Widget? debugRequiredFor,
|
||||
}) {
|
||||
final OverlayState? result = rootOverlay
|
||||
? context.findRootAncestorStateOfType<OverlayState>()
|
||||
: context.findAncestorStateOfType<OverlayState>();
|
||||
final OverlayState? result = maybeOf(context, rootOverlay: rootOverlay);
|
||||
assert(() {
|
||||
if (debugRequiredFor != null && result == null) {
|
||||
if (result == null) {
|
||||
final List<DiagnosticsNode> information = <DiagnosticsNode>[
|
||||
ErrorSummary('No Overlay widget found.'),
|
||||
ErrorDescription('${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.'),
|
||||
ErrorHint('The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.'),
|
||||
DiagnosticsProperty<Widget>('The specific widget that failed to find an overlay was', debugRequiredFor, style: DiagnosticsTreeStyle.errorProperty),
|
||||
ErrorDescription('${debugRequiredFor?.runtimeType ?? 'Some'} widgets require an Overlay widget ancestor for correct operation.'),
|
||||
ErrorHint('The most common way to add an Overlay to an application is to include a MaterialApp, CupertinoApp or Navigator widget in the runApp() call.'),
|
||||
if (debugRequiredFor != null) DiagnosticsProperty<Widget>('The specific widget that failed to find an overlay was', debugRequiredFor, style: DiagnosticsTreeStyle.errorProperty),
|
||||
if (context.widget != debugRequiredFor)
|
||||
context.describeElement('The context from which that widget was searching for an overlay was'),
|
||||
];
|
||||
@ -376,7 +385,36 @@ class Overlay extends StatefulWidget {
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return result;
|
||||
return result!;
|
||||
}
|
||||
|
||||
/// The [OverlayState] from the closest instance of [Overlay] that encloses
|
||||
/// the given context, if any.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// OverlayState? overlay = Overlay.maybeOf(context);
|
||||
/// ```
|
||||
///
|
||||
/// If `rootOverlay` is set to true, the state from the furthest instance of
|
||||
/// this class is given instead. Useful for installing overlay entries above
|
||||
/// all subsequent instances of [Overlay].
|
||||
///
|
||||
/// This method can be expensive (it walks the element tree).
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Overlay.of] for a similar function that returns a non-nullable result
|
||||
/// and throws if an [Overlay] is not found.
|
||||
|
||||
static OverlayState? maybeOf(
|
||||
BuildContext context, {
|
||||
bool rootOverlay = false,
|
||||
}) {
|
||||
return rootOverlay
|
||||
? context.findRootAncestorStateOfType<OverlayState>()
|
||||
: context.findAncestorStateOfType<OverlayState>();
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -728,7 +728,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
||||
);
|
||||
_dragInfo!.startDrag();
|
||||
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget)!;
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget);
|
||||
assert(_overlayEntry == null);
|
||||
_overlayEntry = OverlayEntry(builder: _dragInfo!.createProxy);
|
||||
overlay.insert(_overlayEntry!);
|
||||
@ -923,7 +923,7 @@ class SliverReorderableListState extends State<SliverReorderableList> with Ticke
|
||||
}
|
||||
final Widget child = widget.itemBuilder(context, index);
|
||||
assert(child.key != null, 'All list items must have a key');
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget)!;
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: widget);
|
||||
return _ReorderableItem(
|
||||
key: _ReorderableItemGlobalKey(child.key!, index, this),
|
||||
index: index,
|
||||
@ -1310,7 +1310,7 @@ class _DragInfo extends Drag {
|
||||
}
|
||||
|
||||
Offset _overlayOrigin(BuildContext context) {
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: context.widget)!;
|
||||
final OverlayState overlay = Overlay.of(context, debugRequiredFor: context.widget);
|
||||
final RenderBox overlayBox = overlay.context.findRenderObject()! as RenderBox;
|
||||
return overlayBox.localToGlobal(Offset.zero);
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ abstract class TextSelectionControls {
|
||||
List<TextSelectionPoint> endpoints,
|
||||
TextSelectionDelegate delegate,
|
||||
// TODO(chunhtai): Change to ValueListenable<ClipboardStatus>? once
|
||||
// mirgration is done. https://github.com/flutter/flutter/issues/99360
|
||||
// migration is done. https://github.com/flutter/flutter/issues/99360
|
||||
ClipboardStatusNotifier? clipboardStatus,
|
||||
Offset? lastSecondaryTapDownPosition,
|
||||
);
|
||||
@ -793,16 +793,16 @@ class SelectionOverlay {
|
||||
/// a magnifierBuilder will not be provided, or the magnifierBuilder will return null
|
||||
/// on platforms not mobile.
|
||||
///
|
||||
/// This is NOT the souce of truth for if the magnifier is up or not,
|
||||
/// This is NOT the source of truth for if the magnifier is up or not,
|
||||
/// since magnifiers may hide themselves. If this info is needed, check
|
||||
/// [MagnifierController.shown].
|
||||
void showMagnifier(MagnifierOverlayInfoBearer initalInfoBearer) {
|
||||
void showMagnifier(MagnifierOverlayInfoBearer initialInfoBearer) {
|
||||
if (_toolbar != null) {
|
||||
hideToolbar();
|
||||
}
|
||||
|
||||
// Start from empty, so we don't utilize any rememnant values.
|
||||
_magnifierOverlayInfoBearer.value = initalInfoBearer;
|
||||
// Start from empty, so we don't utilize any remnant values.
|
||||
_magnifierOverlayInfoBearer.value = initialInfoBearer;
|
||||
|
||||
// Pre-build the magnifiers so we can tell if we've built something
|
||||
// or not. If we don't build a magnifiers, then we should not
|
||||
@ -1062,8 +1062,7 @@ class SelectionOverlay {
|
||||
OverlayEntry(builder: _buildEndHandle),
|
||||
];
|
||||
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!
|
||||
.insertAll(_handles!);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor).insertAll(_handles!);
|
||||
}
|
||||
|
||||
/// {@template flutter.widgets.SelectionOverlay.hideHandles}
|
||||
@ -1085,7 +1084,7 @@ class SelectionOverlay {
|
||||
return;
|
||||
}
|
||||
_toolbar = OverlayEntry(builder: _buildToolbar);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!.insert(_toolbar!);
|
||||
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor).insert(_toolbar!);
|
||||
}
|
||||
|
||||
bool _buildScheduled = false;
|
||||
|
@ -226,7 +226,7 @@ void main() {
|
||||
final OverlayEntry fakeBeforeOverlayEntry =
|
||||
OverlayEntry(builder: (_) => fakeBefore);
|
||||
|
||||
Overlay.of(context)!.insert(fakeBeforeOverlayEntry);
|
||||
Overlay.of(context).insert(fakeBeforeOverlayEntry);
|
||||
magnifierController.show(
|
||||
context: context,
|
||||
builder: (_) => fakeMagnifier,
|
||||
|
@ -694,7 +694,7 @@ void main() {
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
testWidgets('OverlayState.of() called without Overlay being exist', (WidgetTester tester) async {
|
||||
testWidgets('OverlayState.of() throws when called if an Overlay does not exist', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
@ -712,7 +712,8 @@ void main() {
|
||||
expect(error.diagnostics[2].level, DiagnosticLevel.hint);
|
||||
expect(error.diagnostics[2].toStringDeep(), equalsIgnoringHashCodes(
|
||||
'The most common way to add an Overlay to an application is to\n'
|
||||
'include a MaterialApp or Navigator widget in the runApp() call.\n',
|
||||
'include a MaterialApp, CupertinoApp or Navigator widget in the\n'
|
||||
'runApp() call.\n'
|
||||
));
|
||||
expect(error.diagnostics[3], isA<DiagnosticsProperty<Widget>>());
|
||||
expect(error.diagnostics[3].value, debugRequiredFor);
|
||||
@ -723,12 +724,13 @@ void main() {
|
||||
' Container widgets require an Overlay widget ancestor for correct\n'
|
||||
' operation.\n'
|
||||
' The most common way to add an Overlay to an application is to\n'
|
||||
' include a MaterialApp or Navigator widget in the runApp() call.\n'
|
||||
' include a MaterialApp, CupertinoApp or Navigator widget in the\n'
|
||||
' runApp() call.\n'
|
||||
' The specific widget that failed to find an overlay was:\n'
|
||||
' Container\n'
|
||||
' The context from which that widget was searching for an overlay\n'
|
||||
' was:\n'
|
||||
' Builder\n',
|
||||
' Builder\n'
|
||||
));
|
||||
}
|
||||
return Container();
|
||||
@ -738,6 +740,47 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets("OverlayState.maybeOf() works when an Overlay does and doesn't exist", (WidgetTester tester) async {
|
||||
final GlobalKey overlayKey = GlobalKey();
|
||||
OverlayState? foundState;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Overlay(
|
||||
key: overlayKey,
|
||||
initialEntries: <OverlayEntry>[
|
||||
OverlayEntry(
|
||||
builder: (BuildContext context) {
|
||||
foundState = Overlay.maybeOf(context);
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(foundState, isNotNull);
|
||||
foundState = null;
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
foundState = Overlay.maybeOf(context);
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.takeException(), isNull);
|
||||
expect(foundState, isNull);
|
||||
});
|
||||
|
||||
testWidgets('OverlayEntry.opaque can be changed when OverlayEntry is not part of an Overlay (yet)', (WidgetTester tester) async {
|
||||
final GlobalKey<OverlayState> overlayKey = GlobalKey<OverlayState>();
|
||||
final Key root = UniqueKey();
|
||||
|
Loading…
Reference in New Issue
Block a user