From b88ba79c04e0e2816282dea0d7ab7ecf71a54179 Mon Sep 17 00:00:00 2001 From: Volodymyr Lykhonis Date: Mon, 5 Feb 2018 14:01:55 -0800 Subject: [PATCH] Customized Cupertino navigation bar. (#14307) - Add override of border color of CupertinoNavigationBar - Add background color to CupertinoPageScaffold --- packages/flutter/lib/src/cupertino/icons.dart | 2 +- .../flutter/lib/src/cupertino/nav_bar.dart | 38 +++++--- .../lib/src/cupertino/page_scaffold.dart | 14 ++- .../flutter/test/cupertino/nav_bar_test.dart | 86 +++++++++++++++++++ .../flutter/test/cupertino/scaffold_test.dart | 49 +++++++++++ 5 files changed, 174 insertions(+), 15 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/icons.dart b/packages/flutter/lib/src/cupertino/icons.dart index caac32a8002..5e94d6a27dc 100644 --- a/packages/flutter/lib/src/cupertino/icons.dart +++ b/packages/flutter/lib/src/cupertino/icons.dart @@ -79,7 +79,7 @@ class CupertinoIcons { /// A checkmark in a circle. static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage); - /// A thicker left chevron used in iOS for the nav bar back button. + /// A thicker left chevron used in iOS for the navigation bar back button. static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true); /// Outline of a simple front-facing house. diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index d710bb154c2..f10202ec1ec 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart @@ -37,6 +37,14 @@ const Duration _kNavBarTitleFadeDuration = const Duration(milliseconds: 150); const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8); const Color _kDefaultNavBarBorderColor = const Color(0x4C000000); +const Border _kDefaultNavBarBorder = const Border( + bottom: const BorderSide( + color: _kDefaultNavBarBorderColor, + width: 0.0, // One physical pixel. + style: BorderStyle.solid, + ), +); + const TextStyle _kLargeTitleTextStyle = const TextStyle( fontFamily: '.SF UI Text', fontSize: 34.0, @@ -77,6 +85,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe this.automaticallyImplyLeading: true, this.middle, this.trailing, + this.border: _kDefaultNavBarBorder, this.backgroundColor: _kDefaultNavBarBackgroundColor, this.actionsForegroundColor: CupertinoColors.activeBlue, }) : assert(automaticallyImplyLeading != null), @@ -109,6 +118,11 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe /// behind it. final Color backgroundColor; + /// The border of the navigation bar. By default renders a single pixel bottom border side. + /// + /// If a border is null, the navigation bar will not display a border. + final Border border; + /// Default color used for text and icons of the [leading] and [trailing] /// widgets in the navigation bar. /// @@ -128,6 +142,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe @override Widget build(BuildContext context) { return _wrapWithBackground( + border: border, backgroundColor: backgroundColor, child: new _CupertinoPersistentNavigationBar( leading: leading, @@ -265,16 +280,14 @@ class CupertinoSliverNavigationBar extends StatelessWidget { /// Returns `child` wrapped with background and a bottom border if background color /// is opaque. Otherwise, also blur with [BackdropFilter]. -Widget _wrapWithBackground({Color backgroundColor, Widget child}) { +Widget _wrapWithBackground({ + Border border, + Color backgroundColor, + Widget child, +}) { final DecoratedBox childWithBackground = new DecoratedBox( decoration: new BoxDecoration( - border: const Border( - bottom: const BorderSide( - color: _kDefaultNavBarBorderColor, - width: 0.0, // One physical pixel. - style: BorderStyle.solid, - ), - ), + border: border, color: backgroundColor, ), child: child, @@ -282,7 +295,8 @@ Widget _wrapWithBackground({Color backgroundColor, Widget child}) { final bool darkBackground = backgroundColor.computeLuminance() < 0.179; SystemChrome.setSystemUIOverlayStyle( - darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark); + darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark + ); if (backgroundColor.alpha == 0xFF) return childWithBackground; @@ -424,7 +438,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate this.automaticallyImplyLeading, this.middle, this.trailing, - this.backgroundColor, + this.border: _kDefaultNavBarBorder, + this.backgroundColor: _kDefaultNavBarBackgroundColor, this.actionsForegroundColor, }) : assert(persistentHeight != null); @@ -442,6 +457,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate final Color backgroundColor; + final Border border; + final Color actionsForegroundColor; @override @@ -467,6 +484,7 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate ); return _wrapWithBackground( + border: border, backgroundColor: backgroundColor, child: new Stack( fit: StackFit.expand, diff --git a/packages/flutter/lib/src/cupertino/page_scaffold.dart b/packages/flutter/lib/src/cupertino/page_scaffold.dart index b5d26917e37..6d9f1beb7f4 100644 --- a/packages/flutter/lib/src/cupertino/page_scaffold.dart +++ b/packages/flutter/lib/src/cupertino/page_scaffold.dart @@ -22,6 +22,7 @@ class CupertinoPageScaffold extends StatelessWidget { const CupertinoPageScaffold({ Key key, this.navigationBar, + this.backgroundColor: CupertinoColors.white, @required this.child, }) : assert(child != null), super(key: key); @@ -32,7 +33,7 @@ class CupertinoPageScaffold extends StatelessWidget { /// If translucent, the main content may slide behind it. /// Otherwise, the main content's top margin will be offset by its height. /// - /// The scaffold assumes the nav bar will consume the [MediaQuery] top padding. + /// The scaffold assumes the navigation bar will consume the [MediaQuery] top padding. // TODO(xster): document its page transition animation when ready final ObstructingPreferredSizeWidget navigationBar; @@ -44,6 +45,11 @@ class CupertinoPageScaffold extends StatelessWidget { /// [navigationBar]. final Widget child; + /// The color of the widget that underlies the entire scaffold. + /// + /// By default uses [CupertinoColors.white] color. + final Color backgroundColor; + @override Widget build(BuildContext context) { final List stacked = []; @@ -57,8 +63,8 @@ class CupertinoPageScaffold extends StatelessWidget { final double topPadding = navigationBar.preferredSize.height + existingMediaQuery.padding.top; - // If nav bar is opaquely obstructing, directly shift the main content - // down. If translucent, let main content draw behind nav bar but hint the + // If navigation bar is opaquely obstructing, directly shift the main content + // down. If translucent, let main content draw behind navigation bar but hint the // obstructed area. if (navigationBar.fullObstruction) { paddedContent = new Padding( @@ -90,7 +96,7 @@ class CupertinoPageScaffold extends StatelessWidget { } return new DecoratedBox( - decoration: const BoxDecoration(color: CupertinoColors.white), + decoration: new BoxDecoration(color: backgroundColor), child: new Stack( children: stacked, ), diff --git a/packages/flutter/test/cupertino/nav_bar_test.dart b/packages/flutter/test/cupertino/nav_bar_test.dart index 2bc3b63d8fe..ece86e58b99 100644 --- a/packages/flutter/test/cupertino/nav_bar_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_test.dart @@ -395,6 +395,92 @@ void main() { expect(find.text('Home page'), findsOneWidget); }); + + testWidgets('Border should be displayed by default', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const CupertinoNavigationBar( + middle: const Text('Title'), + ); + }, + ); + }, + ), + ); + + final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1); + expect(decoratedBox.decoration.runtimeType, BoxDecoration); + + final BoxDecoration decoration = decoratedBox.decoration; + expect(decoration.border, isNotNull); + + final BorderSide side = decoration.border.bottom; + expect(side, isNotNull); + }); + + testWidgets('Overrides border color', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const CupertinoNavigationBar( + middle: const Text('Title'), + border: const Border( + bottom: const BorderSide( + color: const Color(0xFFAABBCC), + width: 0.0, + ), + ), + ); + }, + ); + }, + ), + ); + + final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1); + expect(decoratedBox.decoration.runtimeType, BoxDecoration); + + final BoxDecoration decoration = decoratedBox.decoration; + expect(decoration.border, isNotNull); + + final BorderSide side = decoration.border.bottom; + expect(side, isNotNull); + expect(side.color, const Color(0xFFAABBCC)); + }); + + testWidgets('Border should not be displayed when null', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const CupertinoNavigationBar( + middle: const Text('Title'), + border: null, + ); + }, + ); + }, + ), + ); + + final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1); + expect(decoratedBox.decoration.runtimeType, BoxDecoration); + + final BoxDecoration decoration = decoratedBox.decoration; + expect(decoration.border, isNull); + }); } class _ExpectStyles extends StatelessWidget { diff --git a/packages/flutter/test/cupertino/scaffold_test.dart b/packages/flutter/test/cupertino/scaffold_test.dart index eceea198f6d..030b786c992 100644 --- a/packages/flutter/test/cupertino/scaffold_test.dart +++ b/packages/flutter/test/cupertino/scaffold_test.dart @@ -254,4 +254,53 @@ void main() { expect(find.text('Page 1 of tab 2'), isOnstage); expect(find.text('Page 2 of tab 1', skipOffstage: false), isOffstage); }); + + testWidgets('Decorated with white background by default', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const CupertinoPageScaffold( + child: const Center(), + ); + }, + ); + }, + ), + ); + + final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1); + expect(decoratedBox.decoration.runtimeType, BoxDecoration); + + final BoxDecoration decoration = decoratedBox.decoration; + expect(decoration.color, CupertinoColors.white); + }); + + testWidgets('Overrides background color', (WidgetTester tester) async { + await tester.pumpWidget( + new WidgetsApp( + color: const Color(0xFFFFFFFF), + onGenerateRoute: (RouteSettings settings) { + return new CupertinoPageRoute( + settings: settings, + builder: (BuildContext context) { + return const CupertinoPageScaffold( + child: const Center(), + backgroundColor: const Color(0xFF010203), + ); + }, + ); + }, + ), + ); + + final DecoratedBox decoratedBox = tester.widgetList(find.byType(DecoratedBox)).elementAt(1); + expect(decoratedBox.decoration.runtimeType, BoxDecoration); + + final BoxDecoration decoration = decoratedBox.decoration; + expect(decoration.color, const Color(0xFF010203)); + }); }