Unverified Commit 0ba8c2cd authored by LongCatIsLooong's avatar LongCatIsLooong Committed by GitHub

Fix text scale factor for non-content components of Cupertino scaffolds (#38593)

parent 1d8deb1b
...@@ -31,6 +31,14 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000); ...@@ -31,6 +31,14 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000);
/// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by /// If the given [backgroundColor]'s opacity is not 1.0 (which is the case by
/// default), it will produce a blurring effect to the content behind it. /// default), it will produce a blurring effect to the content behind it.
/// ///
/// When used as [CupertinoTabScaffold.tabBar], by default `CupertinoTabBar` has
/// its text scale factor set to 1.0 and does not respond to text scale factor
/// changes from the operating system, to match the native iOS behavior. To override
/// this behavior, wrap each of the `navigationBar`'s components inside a [MediaQuery]
/// with the desired [MediaQueryData.textScaleFactor] value. The text scale factor
/// value from the operating system can be retrieved in many ways, such as querying
/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
///
/// See also: /// See also:
/// ///
/// * [CupertinoTabScaffold], which hosts the [CupertinoTabBar] at the bottom. /// * [CupertinoTabScaffold], which hosts the [CupertinoTabBar] at the bottom.
......
...@@ -179,6 +179,14 @@ bool _isTransitionable(BuildContext context) { ...@@ -179,6 +179,14 @@ bool _isTransitionable(BuildContext context) {
/// Use [transitionBetweenRoutes] or [heroTag] to customize the transition /// Use [transitionBetweenRoutes] or [heroTag] to customize the transition
/// behavior for multiple navigation bars per route. /// behavior for multiple navigation bars per route.
/// ///
/// When used in a [CupertinoPageScaffold], [CupertinoPageScaffold.navigationBar]
/// has its text scale factor set to 1.0 and does not respond to text scale factor
/// changes from the operating system, to match the native iOS behavior. To override
/// this behavior, wrap each of the `navigationBar`'s components inside a [MediaQuery]
/// with the desired [MediaQueryData.textScaleFactor] value. The text scale factor
/// value from the operating system can be retrieved in many ways, such as querying
/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
///
/// See also: /// See also:
/// ///
/// * [CupertinoPageScaffold], a page layout helper typically hosting the /// * [CupertinoPageScaffold], a page layout helper typically hosting the
...@@ -499,6 +507,14 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> { ...@@ -499,6 +507,14 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
/// Use [transitionBetweenRoutes] or [heroTag] to customize the transition /// Use [transitionBetweenRoutes] or [heroTag] to customize the transition
/// behavior for multiple navigation bars per route. /// behavior for multiple navigation bars per route.
/// ///
/// `CupertinoSliverNavigationBar` has its text scale factor set to 1.0 by default
/// and does not respond to text scale factor changes from the operating system,
/// to match the native iOS behavior. To override this behavior, wrap each of the
/// `CupertinoSliverNavigationBar`'s components inside a [MediaQuery] with the
/// desired [MediaQueryData.textScaleFactor] value. The text scale factor value
/// from the operating system can be retrieved in many ways, such as querying
/// [MediaQuery.textScaleFactorOf] against [CupertinoApp]'s [BuildContext].
///
/// See also: /// See also:
/// ///
/// * [CupertinoNavigationBar], an iOS navigation bar for use on non-scrolling /// * [CupertinoNavigationBar], an iOS navigation bar for use on non-scrolling
...@@ -652,20 +668,23 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation ...@@ -652,20 +668,23 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
// Lint ignore to maintain backward compatibility. // Lint ignore to maintain backward compatibility.
widget.actionsForegroundColor, // ignore: deprecated_member_use_from_same_package widget.actionsForegroundColor, // ignore: deprecated_member_use_from_same_package
context, context,
SliverPersistentHeader( MediaQuery(
pinned: true, // iOS navigation bars are always pinned. data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
delegate: _LargeTitleNavigationBarSliverDelegate( child: SliverPersistentHeader(
keys: keys, pinned: true, // iOS navigation bars are always pinned.
components: components, delegate: _LargeTitleNavigationBarSliverDelegate(
userMiddle: widget.middle, keys: keys,
backgroundColor: widget.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor, components: components,
border: widget.border, userMiddle: widget.middle,
padding: widget.padding, backgroundColor: widget.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
actionsForegroundColor: actionsForegroundColor, border: widget.border,
transitionBetweenRoutes: widget.transitionBetweenRoutes, padding: widget.padding,
heroTag: widget.heroTag, actionsForegroundColor: actionsForegroundColor,
persistentHeight: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top, transitionBetweenRoutes: widget.transitionBetweenRoutes,
alwaysShowMiddle: widget.middle != null, heroTag: widget.heroTag,
persistentHeight: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
alwaysShowMiddle: widget.middle != null,
),
), ),
), ),
); );
......
...@@ -34,8 +34,16 @@ class CupertinoPageScaffold extends StatefulWidget { ...@@ -34,8 +34,16 @@ class CupertinoPageScaffold extends StatefulWidget {
/// If translucent, the main content may slide behind it. /// If translucent, the main content may slide behind it.
/// Otherwise, the main content's top margin will be offset by its height. /// Otherwise, the main content's top margin will be offset by its height.
/// ///
/// The scaffold assumes the navigation bar will account for the [MediaQuery] top padding, /// The scaffold assumes the navigation bar will account for the [MediaQuery]
/// also consume it if the navigation bar is opaque. /// top padding, also consume it if the navigation bar is opaque.
///
/// By default `navigationBar` has its text scale factor set to 1.0 and does
/// not respond to text scale factor changes from the operating system, to match
/// the native iOS behavior. To override such behavior, wrap each of the `navigationBar`'s
/// components inside a [MediaQuery] with the desired [MediaQueryData.textScaleFactor]
/// value. The text scale factor value from the operating system can be retrieved
/// in many ways, such as querying [MediaQuery.textScaleFactorOf] against
/// [CupertinoApp]'s [BuildContext].
// TODO(xster): document its page transition animation when ready // TODO(xster): document its page transition animation when ready
final ObstructingPreferredSizeWidget navigationBar; final ObstructingPreferredSizeWidget navigationBar;
...@@ -160,7 +168,10 @@ class _CupertinoPageScaffoldState extends State<CupertinoPageScaffold> { ...@@ -160,7 +168,10 @@ class _CupertinoPageScaffoldState extends State<CupertinoPageScaffold> {
top: 0.0, top: 0.0,
left: 0.0, left: 0.0,
right: 0.0, right: 0.0,
child: widget.navigationBar, child: MediaQuery(
data: existingMediaQuery.copyWith(textScaleFactor: 1),
child: widget.navigationBar,
),
)); ));
} }
......
...@@ -237,6 +237,14 @@ class CupertinoTabScaffold extends StatefulWidget { ...@@ -237,6 +237,14 @@ class CupertinoTabScaffold extends StatefulWidget {
/// If translucent, the main content may slide behind it. /// If translucent, the main content may slide behind it.
/// Otherwise, the main content's bottom margin will be offset by its height. /// Otherwise, the main content's bottom margin will be offset by its height.
/// ///
/// By default `tabBar` has its text scale factor set to 1.0 and does not
/// respond to text scale factor changes from the operating system, to match
/// the native iOS behavior. To override this behavior, wrap each of the `tabBar`'s
/// items inside a [MediaQuery] with the desired [MediaQueryData.textScaleFactor]
/// value. The text scale factor value from the operating system can be retrieved
/// int many ways, such as querying [MediaQuery.textScaleFactorOf] against
/// [CupertinoApp]'s [BuildContext].
///
/// Must not be null. /// Must not be null.
final CupertinoTabBar tabBar; final CupertinoTabBar tabBar;
...@@ -392,23 +400,26 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> { ...@@ -392,23 +400,26 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
// The main content being at the bottom is added to the stack first. // The main content being at the bottom is added to the stack first.
stacked.add(content); stacked.add(content);
if (widget.tabBar != null) { stacked.add(
stacked.add(Align( MediaQuery(
alignment: Alignment.bottomCenter, data: existingMediaQuery.copyWith(textScaleFactor: 1),
// Override the tab bar's currentIndex to the current tab and hook in child: Align(
// our own listener to update the [_controller.currentIndex] on top of a possibly user alignment: Alignment.bottomCenter,
// provided callback. // Override the tab bar's currentIndex to the current tab and hook in
child: widget.tabBar.copyWith( // our own listener to update the [_controller.currentIndex] on top of a possibly user
currentIndex: _controller.index, // provided callback.
onTap: (int newIndex) { child: widget.tabBar.copyWith(
_controller.index = newIndex; currentIndex: _controller.index,
// Chain the user's original callback. onTap: (int newIndex) {
if (widget.tabBar.onTap != null) _controller.index = newIndex;
// Chain the user's original callback.
if (widget.tabBar.onTap != null)
widget.tabBar.onTap(newIndex); widget.tabBar.onTap(newIndex);
}, },
),
), ),
)); ),
} );
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
......
...@@ -221,6 +221,48 @@ void main() { ...@@ -221,6 +221,48 @@ void main() {
expect(tester.state<EditableTextState>(find.byType(EditableText)), editableState); expect(tester.state<EditableTextState>(find.byType(EditableText)), editableState);
expect(find.text("don't lose me"), findsOneWidget); expect(find.text("don't lose me"), findsOneWidget);
}); });
testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Builder(builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: List<BottomNavigationBarItem>.generate(
10,
(int i) => BottomNavigationBarItem(icon: const ImageIcon(TestImageProvider(24, 23)), title: Text('$i'))
),
),
tabBuilder: (BuildContext context, int index) => const Text('content'),
),
);
}),
),
);
final Iterable<RichText> barItems = tester.widgetList<RichText>(
find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(RichText),
),
);
final Iterable<RichText> contents = tester.widgetList<RichText>(
find.descendant(
of: find.text('content'),
matching: find.byType(RichText),
skipOffstage: false,
),
);
expect(barItems.length, greaterThan(0));
expect(barItems.any((RichText t) => t.textScaleFactor != 1), isFalse);
expect(contents.length, greaterThan(0));
expect(contents.any((RichText t) => t.textScaleFactor != 99), isFalse);
});
} }
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) { CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
......
...@@ -1018,6 +1018,91 @@ void main() { ...@@ -1018,6 +1018,91 @@ void main() {
expect(backPressed, true); expect(backPressed, true);
} }
); );
testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: Builder(builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
const CupertinoSliverNavigationBar(
leading: Text('leading'),
middle: Text('middle'),
largeTitle: Text('Large Title'),
trailing: Text('trailing'),
),
SliverToBoxAdapter(
child: Container(
child: const Text('content'),
),
),
],
),
),
);
}),
),
);
final Iterable<RichText> barItems = tester.widgetList<RichText>(
find.descendant(
of: find.byType(CupertinoSliverNavigationBar),
matching: find.byType(RichText),
),
);
final Iterable<RichText> contents = tester.widgetList<RichText>(
find.descendant(
of: find.text('content'),
matching: find.byType(RichText),
),
);
expect(barItems.length, greaterThan(0));
expect(barItems.any((RichText t) => t.textScaleFactor != 1), isFalse);
expect(contents.length, greaterThan(0));
expect(contents.any((RichText t) => t.textScaleFactor != 99), isFalse);
// Also works with implicitly added widgets.
tester.state<NavigatorState>(find.byType(Navigator)).push(CupertinoPageRoute<void>(
title: 'title',
builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: Container(
child: const CupertinoPageScaffold(
child: CustomScrollView(
slivers: <Widget>[
CupertinoSliverNavigationBar(
automaticallyImplyLeading: true,
automaticallyImplyTitle: true,
previousPageTitle: 'previous title',
),
],
),
),
),
);
},
));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
final Iterable<RichText> barItems2 = tester.widgetList<RichText>(
find.descendant(
of: find.byType(CupertinoSliverNavigationBar),
matching: find.byType(RichText),
),
);
expect(barItems2.length, greaterThan(0));
expect(barItems2.any((RichText t) => t.textScaleFactor != 1), isFalse);
});
} }
class _ExpectStyles extends StatelessWidget { class _ExpectStyles extends StatelessWidget {
......
...@@ -481,4 +481,32 @@ void main() { ...@@ -481,4 +481,32 @@ void main() {
expect(positionWithInsetNoNavBar.dy, lessThan(positionNoInsetNoNavBar.dy)); expect(positionWithInsetNoNavBar.dy, lessThan(positionNoInsetNoNavBar.dy));
expect(positionWithInsetNoNavBar, equals(positionWithInsetWithNavBar)); expect(positionWithInsetNoNavBar, equals(positionWithInsetWithNavBar));
}); });
testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: Builder(builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('middle'),
leading: Text('leading'),
trailing: Text('trailing'),
),
child: Text('content'),
),
);
}),
),
);
final Iterable<RichText> richTextList = tester.widgetList<RichText>(
find.descendant(of: find.byType(CupertinoNavigationBar), matching: find.byType(RichText)),
);
expect(richTextList.length, greaterThan(0));
expect(richTextList.any((RichText text) => text.textScaleFactor != 1), isFalse);
expect(tester.widget<RichText>(find.descendant(of: find.text('content'), matching: find.byType(RichText))).textScaleFactor, 99);
});
} }
...@@ -981,6 +981,48 @@ void main() { ...@@ -981,6 +981,48 @@ void main() {
expect(tester.state<EditableTextState>(find.byType(EditableText)), editableState); expect(tester.state<EditableTextState>(find.byType(EditableText)), editableState);
expect(find.text("don't lose me"), findsOneWidget); expect(find.text("don't lose me"), findsOneWidget);
}); });
testWidgets('textScaleFactor is set to 1.0', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: Builder(builder: (BuildContext context) {
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 99),
child: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: List<BottomNavigationBarItem>.generate(
10,
(int i) => BottomNavigationBarItem(icon: const ImageIcon(TestImageProvider(24, 23)), title: Text('$i'))
),
),
tabBuilder: (BuildContext context, int index) => const Text('content'),
),
);
}),
),
);
final Iterable<RichText> barItems = tester.widgetList<RichText>(
find.descendant(
of: find.byType(CupertinoTabBar),
matching: find.byType(RichText),
),
);
final Iterable<RichText> contents = tester.widgetList<RichText>(
find.descendant(
of: find.text('content'),
matching: find.byType(RichText),
skipOffstage: false,
),
);
expect(barItems.length, greaterThan(0));
expect(barItems.any((RichText t) => t.textScaleFactor != 1), isFalse);
expect(contents.length, greaterThan(0));
expect(contents.any((RichText t) => t.textScaleFactor != 99), isFalse);
});
} }
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) { CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
......
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