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(