diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index c568069e2f1..8314292dcd6 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -105,7 +105,8 @@ class MediaQueryData { this.disableAnimations = false, this.boldText = false, this.navigationMode = NavigationMode.traditional, - this.gestureSettings = const DeviceGestureSettings(touchSlop: kTouchSlop) + this.gestureSettings = const DeviceGestureSettings(touchSlop: kTouchSlop), + this.displayFeatures = const [], }) : assert(size != null), assert(devicePixelRatio != null), assert(textScaleFactor != null), @@ -121,7 +122,8 @@ class MediaQueryData { assert(disableAnimations != null), assert(boldText != null), assert(navigationMode != null), - assert(gestureSettings != null); + assert(gestureSettings != null), + assert(displayFeatures != null); /// Creates data for a media query based on the given window. /// @@ -146,7 +148,8 @@ class MediaQueryData { highContrast = window.accessibilityFeatures.highContrast, alwaysUse24HourFormat = window.alwaysUse24HourFormat, navigationMode = NavigationMode.traditional, - gestureSettings = DeviceGestureSettings.fromWindow(window); + gestureSettings = DeviceGestureSettings.fromWindow(window), + displayFeatures = window.displayFeatures; /// The size of the media in logical pixels (e.g, the size of the screen). /// @@ -351,6 +354,17 @@ class MediaQueryData { /// gesture behavior over the framework constants. final DeviceGestureSettings gestureSettings; + /// {@macro dart.ui.ViewConfiguration.displayFeatures} + /// + /// See also: + /// + /// * [dart:ui.DisplayFeatureType], which lists the different types of + /// display features and explains the differences between them. + /// * [dart:ui.DisplayFeatureState], which lists the possible states for + /// folding features ([dart:ui.DisplayFeatureType.fold] and + /// [dart:ui.DisplayFeatureType.hinge]). + final List displayFeatures; + /// The orientation of the media (e.g., whether the device is in landscape or /// portrait mode). Orientation get orientation { @@ -376,6 +390,7 @@ class MediaQueryData { bool? boldText, NavigationMode? navigationMode, DeviceGestureSettings? gestureSettings, + List? displayFeatures, }) { return MediaQueryData( size: size ?? this.size, @@ -394,6 +409,7 @@ class MediaQueryData { boldText: boldText ?? this.boldText, navigationMode: navigationMode ?? this.navigationMode, gestureSettings: gestureSettings ?? this.gestureSettings, + displayFeatures: displayFeatures ?? this.displayFeatures, ); } @@ -445,6 +461,7 @@ class MediaQueryData { accessibleNavigation: accessibleNavigation, boldText: boldText, gestureSettings: gestureSettings, + displayFeatures: displayFeatures, ); } @@ -494,6 +511,7 @@ class MediaQueryData { accessibleNavigation: accessibleNavigation, boldText: boldText, gestureSettings: gestureSettings, + displayFeatures: displayFeatures, ); } @@ -543,6 +561,7 @@ class MediaQueryData { accessibleNavigation: accessibleNavigation, boldText: boldText, gestureSettings: gestureSettings, + displayFeatures: displayFeatures, ); } @@ -565,7 +584,8 @@ class MediaQueryData { && other.accessibleNavigation == accessibleNavigation && other.boldText == boldText && other.navigationMode == navigationMode - && other.gestureSettings == gestureSettings; + && other.gestureSettings == gestureSettings + && listEquals(other.displayFeatures, displayFeatures); } @override @@ -586,6 +606,7 @@ class MediaQueryData { boldText, navigationMode, gestureSettings, + hashList(displayFeatures), ); } @@ -607,6 +628,7 @@ class MediaQueryData { 'boldText: $boldText', 'navigationMode: ${navigationMode.name}', 'gestureSettings: $gestureSettings', + 'displayFeatures: $displayFeatures', ]; return '${objectRuntimeType(this, 'MediaQueryData')}(${properties.join(', ')})'; } diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart index 48c54f6804e..85055a8cb6d 100644 --- a/packages/flutter/test/widgets/media_query_test.dart +++ b/packages/flutter/test/widgets/media_query_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show Brightness; +import 'dart:ui' show Brightness, DisplayFeature, DisplayFeatureState, DisplayFeatureType; import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; @@ -112,6 +112,7 @@ void main() { expect(data.highContrast, false); expect(data.platformBrightness, Brightness.light); expect(data.gestureSettings.touchSlop, null); + expect(data.displayFeatures, isEmpty); }); testWidgets('MediaQueryData.copyWith defaults to source', (WidgetTester tester) async { @@ -132,6 +133,7 @@ void main() { expect(copied.highContrast, data.highContrast); expect(copied.platformBrightness, data.platformBrightness); expect(copied.gestureSettings, data.gestureSettings); + expect(copied.displayFeatures, data.displayFeatures); }); testWidgets('MediaQuery.copyWith copies specified values', (WidgetTester tester) async { @@ -145,6 +147,13 @@ void main() { const EdgeInsets customViewInsets = EdgeInsets.all(1.67262); const EdgeInsets customSystemGestureInsets = EdgeInsets.all(1.5556); const DeviceGestureSettings gestureSettings = DeviceGestureSettings(touchSlop: 8.0); + const List customDisplayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; final MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window); final MediaQueryData copied = data.copyWith( @@ -163,6 +172,7 @@ void main() { highContrast: true, platformBrightness: Brightness.dark, gestureSettings: gestureSettings, + displayFeatures: customDisplayFeatures, ); expect(copied.size, customSize); expect(copied.devicePixelRatio, customDevicePixelRatio); @@ -179,6 +189,7 @@ void main() { expect(copied.highContrast, true); expect(copied.platformBrightness, Brightness.dark); expect(copied.gestureSettings, gestureSettings); + expect(copied.displayFeatures, customDisplayFeatures); }); testWidgets('MediaQuery.removePadding removes specified padding', (WidgetTester tester) async { @@ -188,6 +199,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -205,6 +223,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -238,6 +257,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.removePadding only removes specified padding', (WidgetTester tester) async { @@ -247,6 +267,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -264,6 +291,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -294,6 +322,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.removeViewInsets removes specified viewInsets', (WidgetTester tester) async { @@ -303,6 +332,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -320,6 +356,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -353,6 +390,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.removeViewInsets removes only specified viewInsets', (WidgetTester tester) async { @@ -362,6 +400,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -379,6 +424,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -409,6 +455,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.removeViewPadding removes specified viewPadding', (WidgetTester tester) async { @@ -418,6 +465,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -435,6 +489,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -468,6 +523,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.removeViewPadding removes only specified viewPadding', (WidgetTester tester) async { @@ -477,6 +533,13 @@ void main() { const EdgeInsets padding = EdgeInsets.only(top: 5.0, right: 6.0, left: 7.0, bottom: 8.0); const EdgeInsets viewPadding = EdgeInsets.only(top: 6.0, right: 8.0, left: 10.0, bottom: 12.0); const EdgeInsets viewInsets = EdgeInsets.only(top: 1.0, right: 2.0, left: 3.0, bottom: 4.0); + const List displayFeatures = [ + DisplayFeature( + bounds: Rect.zero, + type: DisplayFeatureType.cutout, + state: DisplayFeatureState.unknown, + ), + ]; late MediaQueryData unpadded; await tester.pumpWidget( @@ -494,6 +557,7 @@ void main() { disableAnimations: true, boldText: true, highContrast: true, + displayFeatures: displayFeatures, ), child: Builder( builder: (BuildContext context) { @@ -524,6 +588,7 @@ void main() { expect(unpadded.disableAnimations, true); expect(unpadded.boldText, true); expect(unpadded.highContrast, true); + expect(unpadded.displayFeatures, displayFeatures); }); testWidgets('MediaQuery.textScaleFactorOf', (WidgetTester tester) async { diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 5ee8c49575d..0ef04f61d97 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -134,6 +134,20 @@ class TestWindow implements ui.SingletonFlutterWindow { onMetricsChanged?.call(); } + @override + List get displayFeatures => _displayFeaturesTestValue ?? _window.displayFeatures; + List? _displayFeaturesTestValue; + /// Hides the real displayFeatures and reports the given [displayFeaturesTestValue] instead. + set displayFeaturesTestValue(List displayFeaturesTestValue) { // ignore: avoid_setters_without_getters + _displayFeaturesTestValue = displayFeaturesTestValue; + onMetricsChanged?.call(); + } + /// Deletes any existing test padding and returns to using the real padding. + void clearDisplayFeaturesTestValue() { + _displayFeaturesTestValue = null; + onMetricsChanged?.call(); + } + @override ui.WindowPadding get systemGestureInsets => _systemGestureInsetsTestValue ?? _window.systemGestureInsets; ui.WindowPadding? _systemGestureInsetsTestValue; @@ -427,6 +441,7 @@ class TestWindow implements ui.SingletonFlutterWindow { clearLocaleTestValue(); clearLocalesTestValue(); clearPaddingTestValue(); + clearDisplayFeaturesTestValue(); clearPhysicalSizeTestValue(); clearSemanticsEnabledTestValue(); clearTextScaleFactorTestValue();