// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/widgets.dart'; import 'colors.dart'; /// Implements a single iOS application page's layout. /// /// The scaffold lays out the navigation bar on top and the content between or /// behind the navigation bar. /// /// See also: /// /// * [CupertinoTabScaffold], a similar widget for tabbed applications. /// * [CupertinoPageRoute], a modal page route that typically hosts a /// [CupertinoPageScaffold] with support for iOS-style page transitions. class CupertinoPageScaffold extends StatelessWidget { /// Creates a layout for pages with a navigation bar at the top. const CupertinoPageScaffold({ Key key, this.navigationBar, this.backgroundColor = CupertinoColors.white, this.resizeToAvoidBottomInset = true, @required this.child, }) : assert(child != null), assert(resizeToAvoidBottomInset != null), super(key: key); /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the /// top of the screen. /// /// 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 navigation bar will consume the [MediaQuery] top padding. // TODO(xster): document its page transition animation when ready final ObstructingPreferredSizeWidget navigationBar; /// Widget to show in the main content area. /// /// Content can slide under the [navigationBar] when they're translucent. /// In that case, the child's [BuildContext]'s [MediaQuery] will have a /// top padding indicating the area of obstructing overlap from the /// [navigationBar]. final Widget child; /// The color of the widget that underlies the entire scaffold. /// /// By default uses [CupertinoColors.white] color. final Color backgroundColor; /// Whether the [child] should size itself to avoid the window's bottom inset. /// /// For example, if there is an onscreen keyboard displayed above the /// scaffold, the body can be resized to avoid overlapping the keyboard, which /// prevents widgets inside the body from being obscured by the keyboard. /// /// Defaults to true. final bool resizeToAvoidBottomInset; @override Widget build(BuildContext context) { final List stacked = []; Widget paddedContent = child; if (navigationBar != null) { final MediaQueryData existingMediaQuery = MediaQuery.of(context); // TODO(xster): Use real size after partial layout instead of preferred size. // https://github.com/flutter/flutter/issues/12912 final double topPadding = navigationBar.preferredSize.height + existingMediaQuery.padding.top; // Propagate bottom padding and include viewInsets if appropriate final double bottomPadding = resizeToAvoidBottomInset ? existingMediaQuery.viewInsets.bottom : 0.0; // 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 = Padding( padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding), child: child, ); } else { paddedContent = MediaQuery( data: existingMediaQuery.copyWith( padding: existingMediaQuery.padding.copyWith( top: topPadding, ), ), child: Padding( padding: EdgeInsets.only(bottom: bottomPadding), child: child, ), ); } } // The main content being at the bottom is added to the stack first. stacked.add(paddedContent); if (navigationBar != null) { stacked.add(Positioned( top: 0.0, left: 0.0, right: 0.0, child: navigationBar, )); } return DecoratedBox( decoration: BoxDecoration(color: backgroundColor), child: Stack( children: stacked, ), ); } } /// Widget that has a preferred size and reports whether it fully obstructs /// widgets behind it. /// /// Used by [CupertinoPageScaffold] to either shift away fully obstructed content /// or provide a padding guide to partially obstructed content. abstract class ObstructingPreferredSizeWidget extends PreferredSizeWidget { /// If true, this widget fully obstructs widgets behind it by the specified /// size. /// /// If false, this widget partially obstructs. bool get fullObstruction; }