Unverified Commit b9bf5769 authored by Attila Szabó's avatar Attila Szabó Committed by GitHub

Override MediaQuery for WidgetsApp (#81295)

parent 27acffe9
......@@ -236,6 +236,9 @@ typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRo
/// It is used by both [MaterialApp] and [CupertinoApp] to implement base
/// functionality for an app.
///
/// If a [MediaQuery] is not available above [WidgetsApp], a [MediaQuery] is
/// built using [MediaQuery.fromWindow].
///
/// Find references to many of the widgets that [WidgetsApp] wraps in the "See
/// also" section.
///
......@@ -247,6 +250,8 @@ typedef InitialRouteListFactory = List<Route<dynamic>> Function(String initialRo
/// without an explicit style.
/// * [MediaQuery], which establishes a subtree in which media queries resolve
/// to a [MediaQueryData].
/// * [MediaQuery.fromWindow], which builds a [MediaQuery] with data derived
/// from [WidgetsBinding.window].
/// * [Localizations], which defines the [Locale] for its `child`.
/// * [Title], a widget that describes this app in the operating system.
/// * [Navigator], a widget that manages a set of child widgets with a stack
......@@ -1635,6 +1640,19 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
assert(_debugCheckLocalizations(appLocale));
Widget child = Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
);
final MediaQueryData? data = MediaQuery.maybeOf(context);
if (data == null) {
child = MediaQuery.fromWindow(
child: child,
);
}
return RootRestorationScope(
restorationId: widget.restorationScopeId,
child: Shortcuts(
......@@ -1648,13 +1666,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
child: DefaultTextEditingActions(
child: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: _MediaQueryFromWindow(
child: Localizations(
locale: appLocale,
delegates: _localizationsDelegates.toList(),
child: title,
),
),
child: child,
),
),
),
......@@ -1663,81 +1675,3 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
);
}
}
/// Builds [MediaQuery] from `window` by listening to [WidgetsBinding].
///
/// It is performed in a standalone widget to rebuild **only** [MediaQuery] and
/// its dependents when `window` changes, instead of rebuilding the entire widget tree.
class _MediaQueryFromWindow extends StatefulWidget {
const _MediaQueryFromWindow({Key? key, required this.child}) : super(key: key);
final Widget child;
@override
_MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();
}
class _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
// ACCESSIBILITY
@override
void didChangeAccessibilityFeatures() {
setState(() {
// The properties of window have changed. We use them in our build
// function, so we need setState(), but we don't cache anything locally.
});
}
// METRICS
@override
void didChangeMetrics() {
setState(() {
// The properties of window have changed. We use them in our build
// function, so we need setState(), but we don't cache anything locally.
});
}
@override
void didChangeTextScaleFactor() {
setState(() {
// The textScaleFactor property of window has changed. We reference
// window in our build function, so we need to call setState(), but
// we don't need to cache anything locally.
});
}
// RENDERING
@override
void didChangePlatformBrightness() {
setState(() {
// The platformBrightness property of window has changed. We reference
// window in our build function, so we need to call setState(), but
// we don't need to cache anything locally.
});
}
@override
Widget build(BuildContext context) {
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
if (!kReleaseMode) {
data = data.copyWith(platformBrightness: debugBrightnessOverride);
}
return MediaQuery(
data: data,
child: widget.child,
);
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
}
......@@ -9,6 +9,7 @@ import 'dart:ui' show Brightness;
import 'package:flutter/foundation.dart';
import 'basic.dart';
import 'binding.dart';
import 'debug.dart';
import 'framework.dart';
......@@ -787,6 +788,32 @@ class MediaQuery extends InheritedWidget {
);
}
/// Creates a [_MediaQueryFromWindow] which builds and updates a
/// [MediaQuery] using the latest [WidgetsBinding.window] values.
///
/// The [MediaQuery] is wrapped in a separate widget to ensure that only it
/// and its dependents are updated when `window` changes, instead of
/// rebuilding the whole widget tree.
///
/// This should be inserted into the widget tree when the [MediaQuery] view
/// padding is consumed by a widget in such a way that the view padding is no
/// longer exposed to the widget's descendants or siblings.
///
/// The [child] argument is required and must not be null.
///
/// See also:
///
/// * [_MediaQueryFromWindow], the underlying widget.
static Widget fromWindow({
Key? key,
required Widget child,
}) {
return _MediaQueryFromWindow(
key: key,
child: child,
);
}
/// Contains information about the current media.
///
/// For example, the [MediaQueryData.size] property contains the width and
......@@ -922,3 +949,100 @@ enum NavigationMode {
/// focus (although they remain disabled) when traversed.
directional,
}
/// Provides a [MediaQuery] which is built and updated using the latest
/// [WidgetsBinding.window] values.
///
/// Receives `window` updates by listening to [WidgetsBinding].
///
/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
/// its dependents when `window` changes, instead of rebuilding the entire
/// widget tree.
///
/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
///
/// See also:
///
/// * [MediaQuery], which establishes a subtree in which media queries resolve
/// to a [MediaQueryData].
class _MediaQueryFromWindow extends StatefulWidget {
/// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
/// descendants using the `window` to keep [MediaQueryData] up to date.
///
/// The [child] must not be null.
const _MediaQueryFromWindow({
Key? key,
required this.child,
}) : super(key: key);
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
@override
State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
}
class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
// ACCESSIBILITY
@override
void didChangeAccessibilityFeatures() {
setState(() {
// The properties of window have changed. We use them in our build
// function, so we need setState(), but we don't cache anything locally.
});
}
// METRICS
@override
void didChangeMetrics() {
setState(() {
// The properties of window have changed. We use them in our build
// function, so we need setState(), but we don't cache anything locally.
});
}
@override
void didChangeTextScaleFactor() {
setState(() {
// The textScaleFactor property of window has changed. We reference
// window in our build function, so we need to call setState(), but
// we don't need to cache anything locally.
});
}
// RENDERING
@override
void didChangePlatformBrightness() {
setState(() {
// The platformBrightness property of window has changed. We reference
// window in our build function, so we need to call setState(), but
// we don't need to cache anything locally.
});
}
@override
Widget build(BuildContext context) {
MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
if (!kReleaseMode) {
data = data.copyWith(platformBrightness: debugBrightnessOverride);
}
return MediaQuery(
data: data,
child: widget.child,
);
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
}
......@@ -455,6 +455,39 @@ void main() {
const Locale('zh'),
);
});
testWidgets('WidgetsApp creates a MediaQuery', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
WidgetsApp(
builder: (BuildContext context, Widget? child) {
capturedContext = context;
return const Placeholder();
},
color: const Color(0xFF123456),
),
);
expect(MediaQuery.of(capturedContext), isNotNull);
});
testWidgets('WidgetsApp does not create MediaQuery if one is already available', (WidgetTester tester) async {
late BuildContext capturedContext;
final UniqueKey uniqueKey = UniqueKey();
await tester.pumpWidget(
MediaQuery(
key: uniqueKey,
data: const MediaQueryData(),
child: WidgetsApp(
builder: (BuildContext context, Widget? child) {
capturedContext = context;
return const Placeholder();
},
color: const Color(0xFF123456),
),
),
);
expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
});
}
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
......
......@@ -627,4 +627,49 @@ void main() {
expect(outsideBoldTextOverride, false);
expect(insideBoldTextOverride, true);
});
testWidgets('MediaQuery.fromWindow creates a MediaQuery', (WidgetTester tester) async {
bool hasMediaQueryAsParentOutside = false;
bool hasMediaQueryAsParentInside = false;
await tester.pumpWidget(
Builder(
builder: (BuildContext context) {
hasMediaQueryAsParentOutside =
context.findAncestorWidgetOfExactType<MediaQuery>() != null;
return MediaQuery.fromWindow(
child: Builder(
builder: (BuildContext context) {
hasMediaQueryAsParentInside =
context.findAncestorWidgetOfExactType<MediaQuery>() != null;
return const SizedBox();
},
),
);
},
),
);
expect(hasMediaQueryAsParentOutside, false);
expect(hasMediaQueryAsParentInside, true);
});
testWidgets('MediaQueryData.fromWindow is created using window values', (WidgetTester tester)
async {
final MediaQueryData windowData = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
late MediaQueryData fromWindowData;
await tester.pumpWidget(
MediaQuery.fromWindow(
child: Builder(
builder: (BuildContext context) {
fromWindowData = MediaQuery.of(context);
return const SizedBox();
},
),
),
);
expect(windowData, equals(fromWindowData));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment