diff --git a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart index 98354c1404..150948aad7 100644 --- a/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart +++ b/packages/flutter/lib/src/cupertino/bottom_tab_bar.dart @@ -31,6 +31,14 @@ const Color _kDefaultTabBarBorderColor = Color(0x4C000000); /// 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. /// +/// 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: /// /// * [CupertinoTabScaffold], which hosts the [CupertinoTabBar] at the bottom. diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index 6f25123a20..281041a7eb 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -179,6 +179,14 @@ bool _isTransitionable(BuildContext context) { /// Use [transitionBetweenRoutes] or [heroTag] to customize the transition /// 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: /// /// * [CupertinoPageScaffold], a page layout helper typically hosting the @@ -499,6 +507,14 @@ class _CupertinoNavigationBarState extends State { /// Use [transitionBetweenRoutes] or [heroTag] to customize the transition /// 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: /// /// * [CupertinoNavigationBar], an iOS navigation bar for use on non-scrolling @@ -652,20 +668,23 @@ class _CupertinoSliverNavigationBarState extends State { top: 0.0, left: 0.0, right: 0.0, - child: widget.navigationBar, + child: MediaQuery( + data: existingMediaQuery.copyWith(textScaleFactor: 1), + child: widget.navigationBar, + ), )); } diff --git a/packages/flutter/lib/src/cupertino/tab_scaffold.dart b/packages/flutter/lib/src/cupertino/tab_scaffold.dart index 59147fe094..f348b62ff2 100644 --- a/packages/flutter/lib/src/cupertino/tab_scaffold.dart +++ b/packages/flutter/lib/src/cupertino/tab_scaffold.dart @@ -237,6 +237,14 @@ class CupertinoTabScaffold extends StatefulWidget { /// If translucent, the main content may slide behind it. /// 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. final CupertinoTabBar tabBar; @@ -392,23 +400,26 @@ class _CupertinoTabScaffoldState extends State { // The main content being at the bottom is added to the stack first. stacked.add(content); - if (widget.tabBar != null) { - stacked.add(Align( - alignment: Alignment.bottomCenter, - // Override the tab bar's currentIndex to the current tab and hook in - // our own listener to update the [_controller.currentIndex] on top of a possibly user - // provided callback. - child: widget.tabBar.copyWith( - currentIndex: _controller.index, - onTap: (int newIndex) { - _controller.index = newIndex; - // Chain the user's original callback. - if (widget.tabBar.onTap != null) + stacked.add( + MediaQuery( + data: existingMediaQuery.copyWith(textScaleFactor: 1), + child: Align( + alignment: Alignment.bottomCenter, + // Override the tab bar's currentIndex to the current tab and hook in + // our own listener to update the [_controller.currentIndex] on top of a possibly user + // provided callback. + child: widget.tabBar.copyWith( + currentIndex: _controller.index, + onTap: (int newIndex) { + _controller.index = newIndex; + // Chain the user's original callback. + if (widget.tabBar.onTap != null) widget.tabBar.onTap(newIndex); - }, + }, + ), ), - )); - } + ), + ); return DecoratedBox( decoration: BoxDecoration( diff --git a/packages/flutter/test/cupertino/material/tab_scaffold_test.dart b/packages/flutter/test/cupertino/material/tab_scaffold_test.dart index facd118008..686704586a 100644 --- a/packages/flutter/test/cupertino/material/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/material/tab_scaffold_test.dart @@ -221,6 +221,48 @@ void main() { expect(tester.state(find.byType(EditableText)), editableState); 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.generate( + 10, + (int i) => BottomNavigationBarItem(icon: const ImageIcon(TestImageProvider(24, 23)), title: Text('$i')) + ), + ), + tabBuilder: (BuildContext context, int index) => const Text('content'), + ), + ); + }), + ), + ); + + final Iterable barItems = tester.widgetList( + find.descendant( + of: find.byType(CupertinoTabBar), + matching: find.byType(RichText), + ), + ); + + final Iterable contents = tester.widgetList( + 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 }) { diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart index 56afa4a783..8517df8e97 100644 --- a/packages/flutter/test/cupertino/nav_bar_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -1018,6 +1018,91 @@ void main() { 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: [ + const CupertinoSliverNavigationBar( + leading: Text('leading'), + middle: Text('middle'), + largeTitle: Text('Large Title'), + trailing: Text('trailing'), + ), + SliverToBoxAdapter( + child: Container( + child: const Text('content'), + ), + ), + ], + ), + ), + ); + }), + ), + ); + + final Iterable barItems = tester.widgetList( + find.descendant( + of: find.byType(CupertinoSliverNavigationBar), + matching: find.byType(RichText), + ), + ); + + final Iterable contents = tester.widgetList( + 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(find.byType(Navigator)).push(CupertinoPageRoute( + title: 'title', + builder: (BuildContext context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 99), + child: Container( + child: const CupertinoPageScaffold( + child: CustomScrollView( + slivers: [ + CupertinoSliverNavigationBar( + automaticallyImplyLeading: true, + automaticallyImplyTitle: true, + previousPageTitle: 'previous title', + ), + ], + ), + ), + ), + ); + }, + )); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + final Iterable barItems2 = tester.widgetList( + 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 { diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart index 93e639cfb8..20b6b6b4e7 100644 --- a/packages/flutter/test/cupertino/scaffold_test.dart +++ b/packages/flutter/test/cupertino/scaffold_test.dart @@ -481,4 +481,32 @@ void main() { expect(positionWithInsetNoNavBar.dy, lessThan(positionNoInsetNoNavBar.dy)); 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 richTextList = tester.widgetList( + 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(find.descendant(of: find.text('content'), matching: find.byType(RichText))).textScaleFactor, 99); + }); } diff --git a/packages/flutter/test/cupertino/tab_scaffold_test.dart b/packages/flutter/test/cupertino/tab_scaffold_test.dart index 0595ed125f..2c86105d46 100644 --- a/packages/flutter/test/cupertino/tab_scaffold_test.dart +++ b/packages/flutter/test/cupertino/tab_scaffold_test.dart @@ -981,6 +981,48 @@ void main() { expect(tester.state(find.byType(EditableText)), editableState); 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.generate( + 10, + (int i) => BottomNavigationBarItem(icon: const ImageIcon(TestImageProvider(24, 23)), title: Text('$i')) + ), + ), + tabBuilder: (BuildContext context, int index) => const Text('content'), + ), + ); + }), + ), + ); + + final Iterable barItems = tester.widgetList( + find.descendant( + of: find.byType(CupertinoTabBar), + matching: find.byType(RichText), + ), + ); + + final Iterable contents = tester.widgetList( + 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 }) {