Audit use of defaultTargetPlatform (#36871)
diff --git a/packages/flutter/lib/src/cupertino/picker.dart b/packages/flutter/lib/src/cupertino/picker.dart index 63a7efb..080cca0 100644 --- a/packages/flutter/lib/src/cupertino/picker.dart +++ b/packages/flutter/lib/src/cupertino/picker.dart
@@ -218,8 +218,18 @@ void _handleSelectedItemChanged(int index) { // Only the haptic engine hardware on iOS devices would produce the // intended effects. - if (defaultTargetPlatform == TargetPlatform.iOS - && index != _lastHapticIndex) { + bool hasSuitableHapticHardware; + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + hasSuitableHapticHardware = true; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + hasSuitableHapticHardware = false; + break; + } + assert(hasSuitableHapticHardware != null); + if (hasSuitableHapticHardware && index != _lastHapticIndex) { _lastHapticIndex = index; HapticFeedback.selectionClick(); }
diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 39c2dec..de6ef64 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart
@@ -425,7 +425,7 @@ } void _emitVibration() { - switch(defaultTargetPlatform) { + switch (defaultTargetPlatform) { case TargetPlatform.iOS: HapticFeedback.lightImpact(); break;
diff --git a/packages/flutter/lib/src/foundation/platform.dart b/packages/flutter/lib/src/foundation/platform.dart index 90b618a..908bf3e 100644 --- a/packages/flutter/lib/src/foundation/platform.dart +++ b/packages/flutter/lib/src/foundation/platform.dart
@@ -11,11 +11,11 @@ /// This is the default value of [ThemeData.platform] (hence the name). Widgets /// from the material library should use [Theme.of] to determine the current /// platform for styling purposes, rather than using [defaultTargetPlatform]. -/// However, if there is widget behavior that depends on the actual underlying -/// platform, then depending on [defaultTargetPlatform] makes sense. -/// [dart.io.Platform.environment] should be used directly only when it's -/// critical to actually know the current platform, without any overrides -/// possible (for example, when a system API is about to be called). +/// Widgets and render objects at lower layers that try to emulate the +/// underlying platform can depend on [defaultTargetPlatform] directly. The +/// [dart.io.Platform] object should only be used directly when it's critical to +/// actually know the current platform, without any overrides possible (for +/// example, when a system API is about to be called). /// /// In a test environment, the platform returned is [TargetPlatform.android] /// regardless of the host platform. (Android was chosen because the tests were
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 28d072d..c4a4712 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -384,11 +384,11 @@ @override final Size preferredSize; - bool _getEffectiveCenterTitle(ThemeData themeData) { + bool _getEffectiveCenterTitle(ThemeData theme) { if (centerTitle != null) return centerTitle; - assert(themeData.platform != null); - switch (themeData.platform) { + assert(theme.platform != null); + switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: return false; @@ -417,7 +417,7 @@ Widget build(BuildContext context) { assert(!widget.primary || debugCheckHasMediaQuery(context)); assert(debugCheckHasMaterialLocalizations(context)); - final ThemeData themeData = Theme.of(context); + final ThemeData theme = Theme.of(context); final AppBarTheme appBarTheme = AppBarTheme.of(context); final ScaffoldState scaffold = Scaffold.of(context, nullOk: true); final ModalRoute<dynamic> parentRoute = ModalRoute.of(context); @@ -429,16 +429,16 @@ IconThemeData overallIconTheme = widget.iconTheme ?? appBarTheme.iconTheme - ?? themeData.primaryIconTheme; + ?? theme.primaryIconTheme; IconThemeData actionsIconTheme = widget.actionsIconTheme ?? appBarTheme.actionsIconTheme ?? overallIconTheme; TextStyle centerStyle = widget.textTheme?.title ?? appBarTheme.textTheme?.title - ?? themeData.primaryTextTheme.title; + ?? theme.primaryTextTheme.title; TextStyle sideStyle = widget.textTheme?.body1 ?? appBarTheme.textTheme?.body1 - ?? themeData.primaryTextTheme.body1; + ?? theme.primaryTextTheme.body1; if (widget.toolbarOpacity != 1.0) { final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity); @@ -477,7 +477,7 @@ Widget title = widget.title; if (title != null) { bool namesRoute; - switch (defaultTargetPlatform) { + switch (theme.platform) { case TargetPlatform.android: case TargetPlatform.fuchsia: namesRoute = true; @@ -524,7 +524,7 @@ leading: leading, middle: title, trailing: actions, - centerMiddle: widget._getEffectiveCenterTitle(themeData), + centerMiddle: widget._getEffectiveCenterTitle(theme), middleSpacing: widget.titleSpacing, ); @@ -584,7 +584,7 @@ } final Brightness brightness = widget.brightness ?? appBarTheme.brightness - ?? themeData.primaryColorBrightness; + ?? theme.primaryColorBrightness; final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark; @@ -596,7 +596,7 @@ child: Material( color: widget.backgroundColor ?? appBarTheme.color - ?? themeData.primaryColor, + ?? theme.primaryColor, elevation: widget.elevation ?? appBarTheme.elevation ?? _defaultElevation,
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 8dbf54b..d947052 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -263,7 +263,7 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> { String _getRouteLabel(MaterialLocalizations localizations) { - switch (defaultTargetPlatform) { + switch (Theme.of(context).platform) { case TargetPlatform.iOS: return ''; case TargetPlatform.android:
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 167c0ab..f23ddf7 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart
@@ -319,7 +319,7 @@ ), )); } else { - switch (defaultTargetPlatform) { + switch (theme.platform) { case TargetPlatform.iOS: label = semanticLabel; break; @@ -587,16 +587,18 @@ final List<Widget> body = <Widget>[]; String label = semanticLabel; + final ThemeData theme = Theme.of(context); + if (title != null) { body.add(Padding( padding: titlePadding, child: DefaultTextStyle( - style: Theme.of(context).textTheme.title, + style: theme.textTheme.title, child: Semantics(namesRoute: true, child: title), ), )); } else { - switch (defaultTargetPlatform) { + switch (theme.platform) { case TargetPlatform.iOS: label = semanticLabel; break;
diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index a55f1e1..6aab9c8 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart
@@ -11,6 +11,7 @@ import 'list_tile.dart'; import 'material.dart'; import 'material_localizations.dart'; +import 'theme.dart'; /// The possible alignments of a [Drawer]. enum DrawerAlignment { @@ -125,7 +126,7 @@ Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); String label = semanticLabel; - switch (defaultTargetPlatform) { + switch (Theme.of(context).platform) { case TargetPlatform.iOS: label = semanticLabel; break; @@ -469,6 +470,17 @@ ), ); } else { + bool platformHasBackButton; + switch (Theme.of(context).platform) { + case TargetPlatform.android: + platformHasBackButton = true; + break; + case TargetPlatform.iOS: + case TargetPlatform.fuchsia: + platformHasBackButton = false; + break; + } + assert(platformHasBackButton != null); return GestureDetector( key: _gestureDetectorKey, onHorizontalDragDown: _handleDragDown, @@ -483,7 +495,7 @@ BlockSemantics( child: GestureDetector( // On Android, the back button is used to dismiss a modal. - excludeFromSemantics: defaultTargetPlatform == TargetPlatform.android, + excludeFromSemantics: platformHasBackButton, onTap: close, child: Semantics( label: MaterialLocalizations.of(context)?.modalBarrierDismissLabel,
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 6c3ac7a..7e7c9ef 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -199,8 +199,10 @@ } if (widget.title != null) { + final ThemeData theme = Theme.of(context); + Widget title; - switch (defaultTargetPlatform) { + switch (theme.platform) { case TargetPlatform.iOS: title = widget.title; break; @@ -212,7 +214,6 @@ ); } - final ThemeData theme = Theme.of(context); final double opacity = settings.toolbarOpacity; if (opacity > 0.0) { TextStyle titleStyle = theme.primaryTextTheme.title;
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index f0e1215..d9053fc 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -748,8 +748,9 @@ assert(position != null); assert(items != null && items.isNotEmpty); assert(debugCheckHasMaterialLocalizations(context)); + String label = semanticLabel; - switch (defaultTargetPlatform) { + switch (Theme.of(context).platform) { case TargetPlatform.iOS: label = semanticLabel; break;
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart index cfa1aee..50ee97c 100644 --- a/packages/flutter/lib/src/material/scrollbar.dart +++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -53,10 +53,9 @@ class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin { ScrollbarPainter _materialPainter; - TargetPlatform _currentPlatform; TextDirection _textDirection; Color _themeColor; - + bool _useCupertinoScrollbar; AnimationController _fadeoutAnimationController; Animation<double> _fadeoutOpacityAnimation; Timer _fadeoutTimer; @@ -77,35 +76,36 @@ @override void didChangeDependencies() { super.didChangeDependencies(); - + assert((() { _useCupertinoScrollbar = null; return true; })()); final ThemeData theme = Theme.of(context); - _currentPlatform = theme.platform; - - switch (_currentPlatform) { + switch (theme.platform) { case TargetPlatform.iOS: // On iOS, stop all local animations. CupertinoScrollbar has its own // animations. _fadeoutTimer?.cancel(); _fadeoutTimer = null; _fadeoutAnimationController.reset(); + _useCupertinoScrollbar = true; break; case TargetPlatform.android: case TargetPlatform.fuchsia: _themeColor = theme.highlightColor.withOpacity(1.0); _textDirection = Directionality.of(context); _materialPainter = _buildMaterialScrollbarPainter(); + _useCupertinoScrollbar = false; break; } + assert(_useCupertinoScrollbar != null); } ScrollbarPainter _buildMaterialScrollbarPainter() { return ScrollbarPainter( - color: _themeColor, - textDirection: _textDirection, - thickness: _kScrollbarThickness, - fadeoutOpacityAnimation: _fadeoutOpacityAnimation, - padding: MediaQuery.of(context).padding, - ); + color: _themeColor, + textDirection: _textDirection, + thickness: _kScrollbarThickness, + fadeoutOpacityAnimation: _fadeoutOpacityAnimation, + padding: MediaQuery.of(context).padding, + ); } bool _handleScrollNotification(ScrollNotification notification) { @@ -116,9 +116,8 @@ // iOS sub-delegates to the CupertinoScrollbar instead and doesn't handle // scroll notifications here. - if (_currentPlatform != TargetPlatform.iOS - && (notification is ScrollUpdateNotification - || notification is OverscrollNotification)) { + if (!_useCupertinoScrollbar && + (notification is ScrollUpdateNotification || notification is OverscrollNotification)) { if (_fadeoutAnimationController.status != AnimationStatus.forward) { _fadeoutAnimationController.forward(); } @@ -143,25 +142,21 @@ @override Widget build(BuildContext context) { - switch (_currentPlatform) { - case TargetPlatform.iOS: - return CupertinoScrollbar( - child: widget.child, - ); - case TargetPlatform.android: - case TargetPlatform.fuchsia: - return NotificationListener<ScrollNotification>( - onNotification: _handleScrollNotification, - child: RepaintBoundary( - child: CustomPaint( - foregroundPainter: _materialPainter, - child: RepaintBoundary( - child: widget.child, - ), - ), - ), - ); + if (_useCupertinoScrollbar) { + return CupertinoScrollbar( + child: widget.child, + ); } - throw FlutterError('Unknown platform for scrollbar insertion'); + return NotificationListener<ScrollNotification>( + onNotification: _handleScrollNotification, + child: RepaintBoundary( + child: CustomPaint( + foregroundPainter: _materialPainter, + child: RepaintBoundary( + child: widget.child, + ), + ), + ), + ); } }
diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index 016cf60..1fa2bfc 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart
@@ -485,7 +485,7 @@ break; } String routeName; - switch (defaultTargetPlatform) { + switch (theme.platform) { case TargetPlatform.iOS: routeName = ''; break;
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index dacad32..820b2d4 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -774,14 +774,26 @@ /// The platform the material widgets should adapt to target. /// - /// Defaults to the current platform. This should be used in order to style UI - /// elements according to platform conventions. + /// Defaults to the current platform, as exposed by [defaultTargetPlatform]. + /// This should be used in order to style UI elements according to platform + /// conventions. /// - /// [Platform.defaultTargetPlatform] should be used directly instead only in - /// rare cases where it's necessary to determine behavior based on the - /// platform. [dart.io.Platform.environment] should be used when it's critical + /// Widgets from the material library should use this getter (via [Theme.of]) + /// to determine the current platform for the purpose of emulating the + /// platform behavior (e.g. scrolling or haptic effects). Widgets and render + /// objects at lower layers that try to emulate the underlying platform + /// platform can depend on [defaultTargetPlatform] directly, or may require + /// that the target platform be provided as an argument. The + /// [dart.io.Platform] object should only be used directly when it's critical /// to actually know the current platform, without any overrides possible (for /// example, when a system API is about to be called). + /// + /// In a test environment, the platform returned is [TargetPlatform.android] + /// regardless of the host platform. (Android was chosen because the tests + /// were originally written assuming Android-like behavior, and we added + /// platform adaptations for iOS later). Tests can check iOS behavior by + /// setting the [platform] of the [Theme] explicitly to [TargetPlatform.iOS], + /// or by setting [debugDefaultTargetPlatformOverride]. final TargetPlatform platform; /// Configures the hit test size of certain Material widgets.
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index d5cf674..1745cdf 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -203,8 +203,13 @@ if (fragmentContext.selectedTime.period == DayPeriod.am) { return; } - if (fragmentContext.targetPlatform == TargetPlatform.android) { - _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation); + switch (fragmentContext.targetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation); + break; + case TargetPlatform.iOS: + break; } _togglePeriod(); } @@ -213,8 +218,13 @@ if (fragmentContext.selectedTime.period == DayPeriod.pm) { return; } - if (fragmentContext.targetPlatform == TargetPlatform.android) { - _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation); + switch (fragmentContext.targetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation); + break; + case TargetPlatform.iOS: + break; } _togglePeriod(); }
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index f770544..67fefbd 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -391,13 +391,8 @@ // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404). void _handleKeyEvent(RawKeyEvent keyEvent) { // Only handle key events on Android. - switch (defaultTargetPlatform) { - case TargetPlatform.android: - break; - case TargetPlatform.iOS: - case TargetPlatform.fuchsia: - return; - } + if (keyEvent.data is! RawKeyEventDataAndroid) + return; if (keyEvent is RawKeyUpEvent) return; @@ -1594,12 +1589,15 @@ /// of the cursor for iOS is approximate and obtained through an eyeball /// comparison. Rect get _getCaretPrototype { - switch(defaultTargetPlatform){ + assert(defaultTargetPlatform != null); + switch (defaultTargetPlatform) { case TargetPlatform.iOS: return Rect.fromLTWH(0.0, 0.0, cursorWidth, preferredLineHeight + 2); - default: + case TargetPlatform.android: + case TargetPlatform.fuchsia: return Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset); } + return null; } @override void performLayout() { @@ -1647,10 +1645,11 @@ if (_cursorOffset != null) caretRect = caretRect.shift(_cursorOffset); - if (_textPainter.getFullHeightForCaret(textPosition, _caretPrototype) != null) { + final double caretHeight = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype); + if (caretHeight != null) { switch (defaultTargetPlatform) { - case TargetPlatform.iOS: { - final double heightDiff = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype) - caretRect.height; + case TargetPlatform.iOS: + final double heightDiff = caretHeight - caretRect.height; // Center the caret vertically along the text. caretRect = Rect.fromLTWH( caretRect.left, @@ -1659,8 +1658,8 @@ caretRect.height, ); break; - } - default: { + case TargetPlatform.android: + case TargetPlatform.fuchsia: // Override the height to take the full height of the glyph at the TextPosition // when not on iOS. iOS has special handling that creates a taller caret. // TODO(garyq): See the TODO for _getCaretPrototype. @@ -1668,10 +1667,9 @@ caretRect.left, caretRect.top - _kCaretHeightOffset, caretRect.width, - _textPainter.getFullHeightForCaret(textPosition, _caretPrototype), + caretHeight, ); break; - } } }
diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart index 37b66e5..d6936b0 100644 --- a/packages/flutter/lib/src/widgets/modal_barrier.dart +++ b/packages/flutter/lib/src/widgets/modal_barrier.dart
@@ -75,7 +75,18 @@ @override Widget build(BuildContext context) { assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context)); - final bool semanticsDismissible = dismissible && defaultTargetPlatform != TargetPlatform.android; + bool platformSupportsDismissingBarrier; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + platformSupportsDismissingBarrier = false; + break; + case TargetPlatform.iOS: + platformSupportsDismissingBarrier = true; + break; + } + assert(platformSupportsDismissingBarrier != null); + final bool semanticsDismissible = dismissible && platformSupportsDismissingBarrier; final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible; return BlockSemantics( child: ExcludeSemantics(