Commit b88ba79c authored by Volodymyr Lykhonis's avatar Volodymyr Lykhonis Committed by Ian Hickson

Customized Cupertino navigation bar. (#14307)

- Add override of border color of CupertinoNavigationBar
- Add background color to CupertinoPageScaffold
parent e810bee6
...@@ -79,7 +79,7 @@ class CupertinoIcons { ...@@ -79,7 +79,7 @@ class CupertinoIcons {
/// A checkmark in a circle. /// A checkmark in a circle.
static const IconData check_mark_circled = const IconData(0xf3fe, fontFamily: iconFont, fontPackage: iconFontPackage); 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); static const IconData back = const IconData(0xf3cf, fontFamily: iconFont, fontPackage: iconFontPackage, matchTextDirection: true);
/// Outline of a simple front-facing house. /// Outline of a simple front-facing house.
......
...@@ -37,6 +37,14 @@ const Duration _kNavBarTitleFadeDuration = const Duration(milliseconds: 150); ...@@ -37,6 +37,14 @@ const Duration _kNavBarTitleFadeDuration = const Duration(milliseconds: 150);
const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8); const Color _kDefaultNavBarBackgroundColor = const Color(0xCCF8F8F8);
const Color _kDefaultNavBarBorderColor = const Color(0x4C000000); 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( const TextStyle _kLargeTitleTextStyle = const TextStyle(
fontFamily: '.SF UI Text', fontFamily: '.SF UI Text',
fontSize: 34.0, fontSize: 34.0,
...@@ -77,6 +85,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe ...@@ -77,6 +85,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
this.automaticallyImplyLeading: true, this.automaticallyImplyLeading: true,
this.middle, this.middle,
this.trailing, this.trailing,
this.border: _kDefaultNavBarBorder,
this.backgroundColor: _kDefaultNavBarBackgroundColor, this.backgroundColor: _kDefaultNavBarBackgroundColor,
this.actionsForegroundColor: CupertinoColors.activeBlue, this.actionsForegroundColor: CupertinoColors.activeBlue,
}) : assert(automaticallyImplyLeading != null), }) : assert(automaticallyImplyLeading != null),
...@@ -109,6 +118,11 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe ...@@ -109,6 +118,11 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
/// behind it. /// behind it.
final Color backgroundColor; 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] /// Default color used for text and icons of the [leading] and [trailing]
/// widgets in the navigation bar. /// widgets in the navigation bar.
/// ///
...@@ -128,6 +142,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe ...@@ -128,6 +142,7 @@ class CupertinoNavigationBar extends StatelessWidget implements ObstructingPrefe
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return _wrapWithBackground( return _wrapWithBackground(
border: border,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
child: new _CupertinoPersistentNavigationBar( child: new _CupertinoPersistentNavigationBar(
leading: leading, leading: leading,
...@@ -265,16 +280,14 @@ class CupertinoSliverNavigationBar extends StatelessWidget { ...@@ -265,16 +280,14 @@ class CupertinoSliverNavigationBar extends StatelessWidget {
/// Returns `child` wrapped with background and a bottom border if background color /// Returns `child` wrapped with background and a bottom border if background color
/// is opaque. Otherwise, also blur with [BackdropFilter]. /// 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( final DecoratedBox childWithBackground = new DecoratedBox(
decoration: new BoxDecoration( decoration: new BoxDecoration(
border: const Border( border: border,
bottom: const BorderSide(
color: _kDefaultNavBarBorderColor,
width: 0.0, // One physical pixel.
style: BorderStyle.solid,
),
),
color: backgroundColor, color: backgroundColor,
), ),
child: child, child: child,
...@@ -282,7 +295,8 @@ Widget _wrapWithBackground({Color backgroundColor, Widget child}) { ...@@ -282,7 +295,8 @@ Widget _wrapWithBackground({Color backgroundColor, Widget child}) {
final bool darkBackground = backgroundColor.computeLuminance() < 0.179; final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark); darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark
);
if (backgroundColor.alpha == 0xFF) if (backgroundColor.alpha == 0xFF)
return childWithBackground; return childWithBackground;
...@@ -424,7 +438,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate ...@@ -424,7 +438,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
this.automaticallyImplyLeading, this.automaticallyImplyLeading,
this.middle, this.middle,
this.trailing, this.trailing,
this.backgroundColor, this.border: _kDefaultNavBarBorder,
this.backgroundColor: _kDefaultNavBarBackgroundColor,
this.actionsForegroundColor, this.actionsForegroundColor,
}) : assert(persistentHeight != null); }) : assert(persistentHeight != null);
...@@ -442,6 +457,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate ...@@ -442,6 +457,8 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
final Color backgroundColor; final Color backgroundColor;
final Border border;
final Color actionsForegroundColor; final Color actionsForegroundColor;
@override @override
...@@ -467,6 +484,7 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate ...@@ -467,6 +484,7 @@ class _CupertinoLargeTitleNavigationBarSliverDelegate
); );
return _wrapWithBackground( return _wrapWithBackground(
border: border,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
child: new Stack( child: new Stack(
fit: StackFit.expand, fit: StackFit.expand,
......
...@@ -22,6 +22,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -22,6 +22,7 @@ class CupertinoPageScaffold extends StatelessWidget {
const CupertinoPageScaffold({ const CupertinoPageScaffold({
Key key, Key key,
this.navigationBar, this.navigationBar,
this.backgroundColor: CupertinoColors.white,
@required this.child, @required this.child,
}) : assert(child != null), }) : assert(child != null),
super(key: key); super(key: key);
...@@ -32,7 +33,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -32,7 +33,7 @@ class CupertinoPageScaffold extends StatelessWidget {
/// 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 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 // TODO(xster): document its page transition animation when ready
final ObstructingPreferredSizeWidget navigationBar; final ObstructingPreferredSizeWidget navigationBar;
...@@ -44,6 +45,11 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -44,6 +45,11 @@ class CupertinoPageScaffold extends StatelessWidget {
/// [navigationBar]. /// [navigationBar].
final Widget child; final Widget child;
/// The color of the widget that underlies the entire scaffold.
///
/// By default uses [CupertinoColors.white] color.
final Color backgroundColor;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> stacked = <Widget>[]; final List<Widget> stacked = <Widget>[];
...@@ -57,8 +63,8 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -57,8 +63,8 @@ class CupertinoPageScaffold extends StatelessWidget {
final double topPadding = navigationBar.preferredSize.height final double topPadding = navigationBar.preferredSize.height
+ existingMediaQuery.padding.top; + existingMediaQuery.padding.top;
// If nav bar is opaquely obstructing, directly shift the main content // If navigation bar is opaquely obstructing, directly shift the main content
// down. If translucent, let main content draw behind nav bar but hint the // down. If translucent, let main content draw behind navigation bar but hint the
// obstructed area. // obstructed area.
if (navigationBar.fullObstruction) { if (navigationBar.fullObstruction) {
paddedContent = new Padding( paddedContent = new Padding(
...@@ -90,7 +96,7 @@ class CupertinoPageScaffold extends StatelessWidget { ...@@ -90,7 +96,7 @@ class CupertinoPageScaffold extends StatelessWidget {
} }
return new DecoratedBox( return new DecoratedBox(
decoration: const BoxDecoration(color: CupertinoColors.white), decoration: new BoxDecoration(color: backgroundColor),
child: new Stack( child: new Stack(
children: stacked, children: stacked,
), ),
......
...@@ -395,6 +395,92 @@ void main() { ...@@ -395,6 +395,92 @@ void main() {
expect(find.text('Home page'), findsOneWidget); 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<Null>(
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<Null>(
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<Null>(
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 { class _ExpectStyles extends StatelessWidget {
......
...@@ -254,4 +254,53 @@ void main() { ...@@ -254,4 +254,53 @@ void main() {
expect(find.text('Page 1 of tab 2'), isOnstage); expect(find.text('Page 1 of tab 2'), isOnstage);
expect(find.text('Page 2 of tab 1', skipOffstage: false), isOffstage); 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<Null>(
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<Null>(
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));
});
} }
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