media_query.dart 32.6 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:math' as math;
6
import 'dart:ui' as ui;
7
import 'dart:ui' show Brightness;
8

9
import 'package:flutter/foundation.dart';
10

11 12 13
import 'basic.dart';
import 'framework.dart';

14 15 16 17
/// Whether in portrait or landscape.
enum Orientation {
  /// Taller than wide.
  portrait,
18

19 20 21
  /// Wider than tall.
  landscape
}
22

23 24 25 26 27 28 29 30
/// Information about a piece of media (e.g., a window).
///
/// For example, the [MediaQueryData.size] property contains the width and
/// height of the current window.
///
/// To obtain the current [MediaQueryData] for a given [BuildContext], use the
/// [MediaQuery.of] function. For example, to obtain the size of the current
/// window, use `MediaQuery.of(context).size`.
31 32 33 34
///
/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
/// exception, unless the `nullOk` argument is set to true, in which case it
/// returns null.
35
///
36
/// ## Insets and Padding
37
///
38 39
/// ![A diagram of padding, viewInsets, and viewPadding in correlation with each
/// other](https://flutter.github.io/assets-for-api-docs/assets/widgets/media_query.png)
40
///
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
/// This diagram illustrates how [padding] relates to [viewPadding] and
/// [viewInsets], shown here in its simplest configuration, as the difference
/// between the two. In cases when the viewInsets exceed the viewPadding, like
/// when a software keyboard is shown below, padding goes to zero rather than a
/// negative value. Therefore, padding is calculated by taking
/// `max(0.0, viewPadding - viewInsets)`.
///
/// {@animation 300 300 https://flutter.github.io/assets-for-api-docs/assets/widgets/window_padding.mp4}
///
/// In this diagram, the black areas represent system UI that the app cannot
/// draw over. The red area represents view padding that the application may not
/// be able to detect gestures in and may not want to draw in. The grey area
/// represents the system keyboard, which can cover over the bottom view padding
/// when visible.
///
/// MediaQueryData includes three [EdgeInsets] values:
/// [padding], [viewPadding], and [viewInsets]. These values reflect the
/// configuration of the device and are used and optionally consumed by widgets
/// that position content within these insets. The padding value defines areas
/// that might not be completely visible, like the display "notch" on the iPhone
/// X. The viewInsets value defines areas that aren't visible at all, typically
/// because they're obscured by the device's keyboard. Similar to viewInsets,
/// viewPadding does not differentiate padding in areas that may be obscured.
/// For example, by using the viewPadding property, padding would defer to the
/// iPhone "safe area" regardless of whether a keyboard is showing.
///
/// The viewInsets and viewPadding are independent values, they're
/// measured from the edges of the MediaQuery widget's bounds. Together they
/// inform the [padding] property. The bounds of the top level MediaQuery
/// created by [WidgetsApp] are the same as the window that contains the app.
///
/// Widgets whose layouts consume space defined by [viewInsets], [viewPadding],
/// or [padding] should enclose their children in secondary MediaQuery
74
/// widgets that reduce those properties by the same amount.
75
/// The [removePadding], [removeViewPadding], and [removeViewInsets] methods are
76
/// useful for this.
77
///
78 79
/// See also:
///
80 81 82
///  * [Scaffold], [SafeArea], [CupertinoTabScaffold], and
///    [CupertinoPageScaffold], all of which are informed by [padding],
///    [viewPadding], and [viewInsets].
83
@immutable
84
class MediaQueryData {
85 86 87
  /// Creates data for a media query with explicit values.
  ///
  /// Consider using [MediaQueryData.fromWindow] to create data based on a
88
  /// [Window].
89
  const MediaQueryData({
90 91 92
    this.size = Size.zero,
    this.devicePixelRatio = 1.0,
    this.textScaleFactor = 1.0,
93
    this.platformBrightness = Brightness.light,
94 95
    this.padding = EdgeInsets.zero,
    this.viewInsets = EdgeInsets.zero,
96
    this.systemGestureInsets = EdgeInsets.zero,
97
    this.viewPadding = EdgeInsets.zero,
98
    this.physicalDepth = double.maxFinite,
99
    this.alwaysUse24HourFormat = false,
100 101
    this.accessibleNavigation = false,
    this.invertColors = false,
102
    this.highContrast = false,
103
    this.disableAnimations = false,
104
    this.boldText = false,
105
  });
106

107
  /// Creates data for a media query based on the given window.
108 109 110 111
  ///
  /// If you use this, you should ensure that you also register for
  /// notifications so that you can update your [MediaQueryData] when the
  /// window's metrics change. For example, see
112
  /// [WidgetsBindingObserver.didChangeMetrics] or [Window.onMetricsChanged].
113
  MediaQueryData.fromWindow(ui.Window window)
114
    : size = window.physicalSize / window.devicePixelRatio,
115
      devicePixelRatio = window.devicePixelRatio,
116
      textScaleFactor = window.textScaleFactor,
117
      platformBrightness = window.platformBrightness,
118
      padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
119
      viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
120
      viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
121
      systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
122
      physicalDepth = window.physicalDepth,
123
      accessibleNavigation = window.accessibilityFeatures.accessibleNavigation,
124
      invertColors = window.accessibilityFeatures.invertColors,
125
      disableAnimations = window.accessibilityFeatures.disableAnimations,
126
      boldText = window.accessibilityFeatures.boldText,
127
      highContrast = false,
128
      alwaysUse24HourFormat = window.alwaysUse24HourFormat;
129

130
  /// The size of the media in logical pixels (e.g, the size of the screen).
131 132 133 134 135
  ///
  /// Logical pixels are roughly the same visual size across devices. Physical
  /// pixels are the size of the actual hardware pixels on the device. The
  /// number of physical pixels per logical pixel is described by the
  /// [devicePixelRatio].
136 137
  final Size size;

138 139 140 141 142
  /// The number of device pixels for each logical pixel. This number might not
  /// be a power of two. Indeed, it might not even be an integer. For example,
  /// the Nexus 6 has a device pixel ratio of 3.5.
  final double devicePixelRatio;

143 144 145 146
  /// The number of font pixels for each logical pixel.
  ///
  /// For example, if the text scale factor is 1.5, text will be 50% larger than
  /// the specified font size.
147 148 149 150 151
  ///
  /// See also:
  ///
  ///  * [MediaQuery.textScaleFactorOf], a convenience method which returns the
  ///    textScaleFactor defined for a [BuildContext].
152 153
  final double textScaleFactor;

154 155 156 157 158 159 160 161 162
  /// The current brightness mode of the host platform.
  ///
  /// For example, starting in Android Pie, battery saver mode asks all apps to
  /// render in a "dark mode".
  ///
  /// Not all platforms necessarily support a concept of brightness mode. Those
  /// platforms will report [Brightness.light] in this property.
  final Brightness platformBrightness;

163 164 165 166 167 168
  /// The parts of the display that are completely obscured by system UI,
  /// typically by the device's keyboard.
  ///
  /// When a mobile device's keyboard is visible `viewInsets.bottom`
  /// corresponds to the top of the keyboard.
  ///
169 170 171 172 173
  /// This value is independent of the [padding] and [viewPadding]. viewPadding
  /// is measured from the edges of the [MediaQuery] widget's bounds. Padding is
  /// calculated based on the viewPadding and viewInsets. The bounds of the top
  /// level MediaQuery created by [WidgetsApp] are the same as the window
  /// (often the mobile device screen) that contains the app.
174 175 176
  ///
  /// See also:
  ///
177 178
  ///  * [ui.window], which provides some additional detail about this property
  ///    and how it relates to [padding] and [viewPadding].
179 180
  final EdgeInsets viewInsets;

181 182
  /// The parts of the display that are partially obscured by system UI,
  /// typically by the hardware display "notches" or the system status bar.
183 184 185 186
  ///
  /// If you consumed this padding (e.g. by building a widget that envelops or
  /// accounts for this padding in its layout in such a way that children are
  /// no longer exposed to this padding), you should remove this padding
187
  /// for subsequent descendants in the widget tree by inserting a new
188 189
  /// [MediaQuery] widget using the [MediaQuery.removePadding] factory.
  ///
190 191
  /// Padding is derived from the values of viewInsets and viewPadding.
  ///
192 193
  /// See also:
  ///
194 195
  ///  * [ui.window], which provides some additional detail about this
  ///    property and how it relates to [viewInsets] and [viewPadding].
196 197
  ///  * [SafeArea], a widget that consumes this padding with a [Padding] widget
  ///    and automatically removes it from the [MediaQuery] for its child.
198
  final EdgeInsets padding;
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
  /// The parts of the display that are partially obscured by system UI,
  /// typically by the hardware display "notches" or the system status bar.
  ///
  /// This value remains the same regardless of whether the system is reporting
  /// other obstructions in the same physical area of the screen. For example, a
  /// software keyboard on the bottom of the screen that may cover and consume
  /// the same area that requires bottom padding will not affect this value.
  ///
  /// This value is independent of the [padding] and [viewInsets]: their values
  /// are measured from the edges of the [MediaQuery] widget's bounds. The
  /// bounds of the top level MediaQuery created by [WidgetsApp] are the
  /// same as the window that contains the app. On mobile devices, this will
  /// typically be the full screen.
  ///
  /// See also:
  ///
216 217
  ///  * [ui.window], which provides some additional detail about this
  ///    property and how it relates to [padding] and [viewInsets].
218 219
  final EdgeInsets viewPadding;

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
  /// The areas along the edges of the display where the system consumes
  /// certain input events and blocks delivery of those events to the app.
  ///
  /// Starting with Android Q, simple swipe gestures that start within the
  /// [systemGestureInsets] areas are used by the system for page navigation
  /// and may not be delivered to the app. Taps and swipe gestures that begin
  /// with a long-press are delivered to the app, but simple press-drag-release
  /// swipe gestures which begin within the area defined by [systemGestureInsets]
  /// may not be.
  ///
  /// Apps should avoid locating gesture detectors within the system gesture
  /// insets area. Apps should feel free to put visual elements within
  /// this area.
  ///
  /// This property is currently only expected to be set to a non-default value
  /// on Android starting with version Q.
  ///
237
  /// {@tool sample --template=stateful_widget_material}
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
  ///
  /// For apps that might be deployed on Android Q devices with full gesture
  /// navigation enabled, use [MediaQuery.systemGestureInsets] with [Padding]
  /// to avoid having the left and right edges of the [Slider] from appearing
  /// within the area reserved for system gesture navigation.
  ///
  /// By default, [Slider]s expand to fill the available width. So, we pad the
  /// left and right sides.
  ///
  /// ```dart
  /// double _currentValue = 0.2;
  ///
  /// @override
  /// Widget build(BuildContext context) {
  ///   EdgeInsets systemGestureInsets = MediaQuery.of(context).systemGestureInsets;
  ///   return Scaffold(
  ///     appBar: AppBar(title: Text('Pad Slider to avoid systemGestureInsets')),
  ///     body: Padding(
  ///       padding: EdgeInsets.only( // only left and right padding are needed here
  ///         left: systemGestureInsets.left,
  ///         right: systemGestureInsets.right,
  ///       ),
  ///       child: Slider(
  ///         value: _currentValue.toDouble(),
  ///         onChanged: (double newValue) {
  ///           setState(() {
  ///             _currentValue = newValue;
  ///           });
  ///         },
  ///       ),
  ///     ),
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  final EdgeInsets systemGestureInsets;

275 276 277 278 279 280 281 282 283 284 285 286 287
  /// The physical depth is the maximum elevation that the Window allows.
  ///
  /// Physical layers drawn at or above this elevation will have their elevation
  /// clamped to this value. This can happen if the physical layer itself has
  /// an elevation larger than the available depth, or if some ancestor of the
  /// layer causes it to have a cumulative elevation that is larger than the
  /// available depth.
  ///
  /// The default value is [double.maxFinite], which is used for platforms that
  /// do not specify a maximum elevation. This property is currently only
  /// expected to be set to a non-default value on Fuchsia.
  final double physicalDepth;

288 289 290 291 292 293 294 295 296 297 298 299 300
  /// Whether to use 24-hour format when formatting time.
  ///
  /// The behavior of this flag is different across platforms:
  ///
  /// - On Android this flag is reported directly from the user settings called
  ///   "Use 24-hour format". It applies to any locale used by the application,
  ///   whether it is the system-wide locale, or the custom locale set by the
  ///   application.
  /// - On iOS this flag is set to true when the user setting called "24-Hour
  ///   Time" is set or the system-wide locale's default uses 24-hour
  ///   formatting.
  final bool alwaysUse24HourFormat;

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
  /// Whether the user is using an accessibility service like TalkBack or
  /// VoiceOver to interact with the application.
  ///
  /// When this setting is true, features such as timeouts should be disabled or
  /// have minimum durations increased.
  ///
  /// See also:
  ///
  ///  * [Window.AccessibilityFeatures], where the setting originates.
  final bool accessibleNavigation;

  /// Whether the device is inverting the colors of the platform.
  ///
  /// This flag is currently only updated on iOS devices.
  ///
  /// See also:
  ///
  ///  * [Window.AccessibilityFeatures], where the setting originates.
  final bool invertColors;

321 322 323 324 325 326 327
  /// Whether the user requested a high contrast between foreground and background
  /// content on iOS, via Settings -> Accessibility -> Increase Contrast.
  ///
  /// This flag is currently only updated on iOS devices that are running iOS 13
  /// or above.
  final bool highContrast;

328 329 330
  /// Whether the platform is requesting that animations be disabled or reduced
  /// as much as possible.
  ///
331 332
  /// See also:
  ///
333 334 335
  ///  * [Window.AccessibilityFeatures], where the setting originates.
  final bool disableAnimations;

336 337 338 339 340 341 342 343
  /// Whether the platform is requesting that text be drawn with a bold font
  /// weight.
  ///
  /// See also:
  ///
  ///  * [Window.AccessibilityFeatures], where the setting originates.
  final bool boldText;

344 345
  /// The orientation of the media (e.g., whether the device is in landscape or
  /// portrait mode).
346 347 348 349
  Orientation get orientation {
    return size.width > size.height ? Orientation.landscape : Orientation.portrait;
  }

350 351
  /// Creates a copy of this media query data but with the given fields replaced
  /// with the new values.
352 353 354 355
  MediaQueryData copyWith({
    Size size,
    double devicePixelRatio,
    double textScaleFactor,
356
    Brightness platformBrightness,
357
    EdgeInsets padding,
358
    EdgeInsets viewPadding,
359
    EdgeInsets viewInsets,
360
    EdgeInsets systemGestureInsets,
361
    double physicalDepth,
362
    bool alwaysUse24HourFormat,
363
    bool highContrast,
364 365 366
    bool disableAnimations,
    bool invertColors,
    bool accessibleNavigation,
367
    bool boldText,
368
  }) {
369
    return MediaQueryData(
370 371 372
      size: size ?? this.size,
      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
      textScaleFactor: textScaleFactor ?? this.textScaleFactor,
373
      platformBrightness: platformBrightness ?? this.platformBrightness,
374
      padding: padding ?? this.padding,
375
      viewPadding: viewPadding ?? this.viewPadding,
376
      viewInsets: viewInsets ?? this.viewInsets,
377
      systemGestureInsets: systemGestureInsets ?? this.systemGestureInsets,
378
      physicalDepth: physicalDepth ?? this.physicalDepth,
379
      alwaysUse24HourFormat: alwaysUse24HourFormat ?? this.alwaysUse24HourFormat,
380
      invertColors: invertColors ?? this.invertColors,
381
      highContrast: highContrast ?? this.highContrast,
382 383
      disableAnimations: disableAnimations ?? this.disableAnimations,
      accessibleNavigation: accessibleNavigation ?? this.accessibleNavigation,
384
      boldText: boldText ?? this.boldText,
385 386 387
    );
  }

388
  /// Creates a copy of this media query data but with the given [padding]s
Ian Hickson's avatar
Ian Hickson committed
389 390 391 392 393 394 395 396
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
397
  ///  * [MediaQuery.removePadding], which uses this method to remove [padding]
Ian Hickson's avatar
Ian Hickson committed
398 399 400
  ///    from the ambient [MediaQuery].
  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
  ///    adds a [Padding] widget.
401
  ///  * [removeViewInsets], the same thing but for [viewInsets].
402
  ///  * [removeViewPadding], the same thing but for [viewPadding].
Ian Hickson's avatar
Ian Hickson committed
403
  MediaQueryData removePadding({
404 405 406 407
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
Ian Hickson's avatar
Ian Hickson committed
408 409 410
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
411
    return MediaQueryData(
Ian Hickson's avatar
Ian Hickson committed
412 413 414
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
415
      platformBrightness: platformBrightness,
Ian Hickson's avatar
Ian Hickson committed
416 417 418 419 420 421
      padding: padding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
422
      viewPadding: viewPadding.copyWith(
423 424 425 426
        left: removeLeft ? math.max(0.0, viewPadding.left - padding.left) : null,
        top: removeTop ? math.max(0.0, viewPadding.top - padding.top) : null,
        right: removeRight ? math.max(0.0, viewPadding.right - padding.right) : null,
        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - padding.bottom) : null,
427
      ),
428
      viewInsets: viewInsets,
429
      alwaysUse24HourFormat: alwaysUse24HourFormat,
430
      highContrast: highContrast,
431 432 433
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
434
      boldText: boldText,
Ian Hickson's avatar
Ian Hickson committed
435 436 437
    );
  }

438 439 440 441 442 443 444 445 446
  /// Creates a copy of this media query data but with the given [viewInsets]
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
447 448
  ///  * [MediaQuery.removeViewInsets], which uses this method to remove
  ///    [viewInsets] from the ambient [MediaQuery].
449
  ///  * [removePadding], the same thing but for [padding].
450
  ///  * [removeViewPadding], the same thing but for [viewPadding].
451
  MediaQueryData removeViewInsets({
452 453 454 455
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
456 457 458
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
459
    return MediaQueryData(
460 461 462
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
463
      platformBrightness: platformBrightness,
464
      padding: padding,
465
      viewPadding: viewPadding.copyWith(
466 467 468 469
        left: removeLeft ? math.max(0.0, viewPadding.left - viewInsets.left) : null,
        top: removeTop ? math.max(0.0, viewPadding.top - viewInsets.top) : null,
        right: removeRight ? math.max(0.0, viewPadding.right - viewInsets.right) : null,
        bottom: removeBottom ? math.max(0.0, viewPadding.bottom - viewInsets.bottom) : null,
470
      ),
471 472 473 474 475 476 477
      viewInsets: viewInsets.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      alwaysUse24HourFormat: alwaysUse24HourFormat,
478
      highContrast: highContrast,
479 480 481
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
482
      boldText: boldText,
483 484 485
    );
  }

486 487 488 489 490 491 492 493 494
  /// Creates a copy of this media query data but with the given [viewPadding]
  /// replaced with zero.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then this
  /// [MediaQueryData] is returned unmodified.
  ///
  /// See also:
  ///
495 496
  ///  * [MediaQuery.removeViewPadding], which uses this method to remove
  ///    [viewPadding] from the ambient [MediaQuery].
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
  ///  * [removePadding], the same thing but for [padding].
  ///  * [removeViewInsets], the same thing but for [viewInsets].
  MediaQueryData removeViewPadding({
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
  }) {
    if (!(removeLeft || removeTop || removeRight || removeBottom))
      return this;
    return MediaQueryData(
      size: size,
      devicePixelRatio: devicePixelRatio,
      textScaleFactor: textScaleFactor,
      platformBrightness: platformBrightness,
      padding: padding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      viewInsets: viewInsets,
      viewPadding: viewPadding.copyWith(
        left: removeLeft ? 0.0 : null,
        top: removeTop ? 0.0 : null,
        right: removeRight ? 0.0 : null,
        bottom: removeBottom ? 0.0 : null,
      ),
      alwaysUse24HourFormat: alwaysUse24HourFormat,
526
      highContrast: highContrast,
527 528 529 530 531 532 533
      disableAnimations: disableAnimations,
      invertColors: invertColors,
      accessibleNavigation: accessibleNavigation,
      boldText: boldText,
    );
  }

534
  @override
535
  bool operator ==(Object other) {
536 537
    if (other.runtimeType != runtimeType)
      return false;
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
    return other is MediaQueryData
        && other.size == size
        && other.devicePixelRatio == devicePixelRatio
        && other.textScaleFactor == textScaleFactor
        && other.platformBrightness == platformBrightness
        && other.padding == padding
        && other.viewPadding == viewPadding
        && other.viewInsets == viewInsets
        && other.physicalDepth == physicalDepth
        && other.alwaysUse24HourFormat == alwaysUse24HourFormat
        && other.highContrast == highContrast
        && other.disableAnimations == disableAnimations
        && other.invertColors == invertColors
        && other.accessibleNavigation == accessibleNavigation
        && other.boldText == boldText;
553 554
  }

555
  @override
556 557 558 559 560
  int get hashCode {
    return hashValues(
      size,
      devicePixelRatio,
      textScaleFactor,
561
      platformBrightness,
562
      padding,
563
      viewPadding,
564
      viewInsets,
565
      physicalDepth,
566
      alwaysUse24HourFormat,
567
      highContrast,
568 569 570
      disableAnimations,
      invertColors,
      accessibleNavigation,
571
      boldText,
572 573
    );
  }
574

575
  @override
576
  String toString() {
577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593
    final List<String> properties = <String>[
      'size: $size',
      'devicePixelRatio: ${devicePixelRatio.toStringAsFixed(1)}',
      'textScaleFactor: ${textScaleFactor.toStringAsFixed(1)}',
      'platformBrightness: $platformBrightness',
      'padding: $padding',
      'viewPadding: $viewPadding',
      'viewInsets: $viewInsets',
      'physicalDepth: $physicalDepth',
      'alwaysUse24HourFormat: $alwaysUse24HourFormat',
      'accessibleNavigation: $accessibleNavigation',
      'highContrast: $highContrast',
      'disableAnimations: $disableAnimations',
      'invertColors: $invertColors',
      'boldText: $boldText',
    ];
    return '${objectRuntimeType(this, 'MediaQueryData')}(${properties.join(', ')})';
594
  }
595 596
}

597
/// Establishes a subtree in which media queries resolve to the given data.
598 599 600 601 602 603 604 605 606
///
/// For example, to learn the size of the current media (e.g., the window
/// containing your app), you can read the [MediaQueryData.size] property from
/// the [MediaQueryData] returned by [MediaQuery.of]:
/// `MediaQuery.of(context).size`.
///
/// Querying the current media using [MediaQuery.of] will cause your widget to
/// rebuild automatically whenever the [MediaQueryData] changes (e.g., if the
/// user rotates their device).
607 608 609 610 611
///
/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an
/// exception, unless the `nullOk` argument is set to true, in which case it
/// returns null.
///
612 613
/// {@youtube 560 315 https://www.youtube.com/watch?v=A3WrA4zAaPw}
///
614 615 616 617 618
/// See also:
///
///  * [WidgetsApp] and [MaterialApp], which introduce a [MediaQuery] and keep
///    it up to date with the current screen metrics as they change.
///  * [MediaQueryData], the data structure that represents the metrics.
619
class MediaQuery extends InheritedWidget {
620 621 622
  /// Creates a widget that provides [MediaQueryData] to its descendants.
  ///
  /// The [data] and [child] arguments must not be null.
623
  const MediaQuery({
624
    Key key,
625
    @required this.data,
626
    @required Widget child,
627 628 629
  }) : assert(child != null),
       assert(data != null),
       super(key: key, child: child);
630

631 632
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified padding.
Ian Hickson's avatar
Ian Hickson committed
633
  ///
634
  /// This should be inserted into the widget tree when the [MediaQuery] padding
635
  /// is consumed by a widget in such a way that the padding is no longer
636
  /// exposed to the widget's descendants or siblings.
637
  ///
Ian Hickson's avatar
Ian Hickson committed
638 639 640 641 642 643 644 645 646 647 648 649 650 651
  /// The [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
  ///  * [SafeArea], which both removes the padding from the [MediaQuery] and
  ///    adds a [Padding] widget.
652 653 654 655 656
  ///  * [MediaQueryData.padding], the affected property of the
  ///    [MediaQueryData].
  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
  ///  * [removeViewPadding], the same thing but for
  ///    [MediaQueryData.viewPadding].
Ian Hickson's avatar
Ian Hickson committed
657 658 659
  factory MediaQuery.removePadding({
    Key key,
    @required BuildContext context,
660 661 662 663
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
Ian Hickson's avatar
Ian Hickson committed
664 665
    @required Widget child,
  }) {
666
    return MediaQuery(
Ian Hickson's avatar
Ian Hickson committed
667 668 669 670 671 672 673
      key: key,
      data: MediaQuery.of(context).removePadding(
        removeLeft: removeLeft,
        removeTop: removeTop,
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
674 675 676 677
      child: child,
    );
  }

678 679
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified view insets.
680 681 682
  ///
  /// This should be inserted into the widget tree when the [MediaQuery] view
  /// insets are consumed by a widget in such a way that the view insets are no
683
  /// longer exposed to the widget's descendants or siblings.
684 685 686 687 688 689 690 691 692 693 694 695 696
  ///
  /// The [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
697 698 699 700 701
  ///  * [MediaQueryData.viewInsets], the affected property of the
  ///    [MediaQueryData].
  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
  ///  * [removeViewPadding], the same thing but for
  ///    [MediaQueryData.viewPadding].
702 703 704
  factory MediaQuery.removeViewInsets({
    Key key,
    @required BuildContext context,
705 706 707 708
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
709 710
    @required Widget child,
  }) {
711
    return MediaQuery(
712 713 714 715
      key: key,
      data: MediaQuery.of(context).removeViewInsets(
        removeLeft: removeLeft,
        removeTop: removeTop,
716 717 718 719 720 721 722
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
      child: child,
    );
  }

723 724
  /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
  /// from the given context, but removes the specified view padding.
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742
  ///
  /// 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 [context] argument is required, must not be null, and must have a
  /// [MediaQuery] in scope.
  ///
  /// The `removeLeft`, `removeTop`, `removeRight`, and `removeBottom` arguments
  /// must not be null. If all four are false (the default) then the returned
  /// [MediaQuery] reuses the ambient [MediaQueryData] unmodified, which is not
  /// particularly useful.
  ///
  /// The [child] argument is required and must not be null.
  ///
  /// See also:
  ///
  ///  * [MediaQueryData.viewPadding], the affected property of the
743
  ///    [MediaQueryData].
744 745
  ///  * [removePadding], the same thing but for [MediaQueryData.padding].
  ///  * [removeViewInsets], the same thing but for [MediaQueryData.viewInsets].
746 747 748 749 750 751 752 753 754 755 756 757 758 759
  factory MediaQuery.removeViewPadding({
    Key key,
    @required BuildContext context,
    bool removeLeft = false,
    bool removeTop = false,
    bool removeRight = false,
    bool removeBottom = false,
    @required Widget child,
  }) {
    return MediaQuery(
      key: key,
      data: MediaQuery.of(context).removeViewPadding(
        removeLeft: removeLeft,
        removeTop: removeTop,
760 761 762
        removeRight: removeRight,
        removeBottom: removeBottom,
      ),
Ian Hickson's avatar
Ian Hickson committed
763 764 765 766
      child: child,
    );
  }

767 768 769 770
  /// Contains information about the current media.
  ///
  /// For example, the [MediaQueryData.size] property contains the width and
  /// height of the current window.
771 772
  final MediaQueryData data;

773 774
  /// The data from the closest instance of this class that encloses the given
  /// context.
775 776
  ///
  /// You can use this function to query the size an orientation of the screen.
777 778
  /// When that information changes, your widget will be scheduled to be
  /// rebuilt, keeping your widget up-to-date.
779 780 781 782 783 784
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// MediaQueryData media = MediaQuery.of(context);
  /// ```
785 786 787 788 789 790
  ///
  /// If there is no [MediaQuery] in scope, then this will throw an exception.
  /// To return null if there is no [MediaQuery], then pass `nullOk: true`.
  ///
  /// If you use this from a widget (e.g. in its build function), consider
  /// calling [debugCheckHasMediaQuery].
791
  static MediaQueryData of(BuildContext context, { bool nullOk = false }) {
Ian Hickson's avatar
Ian Hickson committed
792 793
    assert(context != null);
    assert(nullOk != null);
794
    final MediaQuery query = context.dependOnInheritedWidgetOfExactType<MediaQuery>();
795 796 797 798
    if (query != null)
      return query.data;
    if (nullOk)
      return null;
799 800 801 802 803 804 805 806 807 808
    throw FlutterError.fromParts(<DiagnosticsNode>[
      ErrorSummary('MediaQuery.of() called with a context that does not contain a MediaQuery.'),
      ErrorDescription(
        'No MediaQuery ancestor could be found starting from the context that was passed '
        'to MediaQuery.of(). This can happen because you do not have a WidgetsApp or '
        'MaterialApp widget (those widgets introduce a MediaQuery), or it can happen '
        'if the context you use comes from a widget above those widgets.'
      ),
      context.describeElement('The context used was')
    ]);
809 810
  }

811 812 813 814 815 816
  /// Returns textScaleFactor for the nearest MediaQuery ancestor or 1.0, if
  /// no such ancestor exists.
  static double textScaleFactorOf(BuildContext context) {
    return MediaQuery.of(context, nullOk: true)?.textScaleFactor ?? 1.0;
  }

817 818 819 820 821 822 823 824 825
  /// Returns platformBrightness for the nearest MediaQuery ancestor or
  /// [Brightness.light], if no such ancestor exists.
  ///
  /// Use of this method will cause the given [context] to rebuild any time that
  /// any property of the ancestor [MediaQuery] changes.
  static Brightness platformBrightnessOf(BuildContext context) {
    return MediaQuery.of(context, nullOk: true)?.platformBrightness ?? Brightness.light;
  }

826 827 828 829 830 831
  /// Returns the boldText accessibility setting for the nearest MediaQuery
  /// ancestor, or false if no such ancestor exists.
  static bool boldTextOverride(BuildContext context) {
    return MediaQuery.of(context, nullOk: true)?.boldText ?? false;
  }

832
  @override
833
  bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;
834

835
  @override
836 837
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
838
    properties.add(DiagnosticsProperty<MediaQueryData>('data', data, showName: false));
839 840
  }
}