您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1857 行
71 KiB
1857 行
71 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.UIWidgets.animation;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.service;
|
|
using Unity.UIWidgets.ui;
|
|
using Unity.UIWidgets.widgets;
|
|
using UnityEngine;
|
|
using Color = Unity.UIWidgets.ui.Color;
|
|
using Rect = Unity.UIWidgets.ui.Rect;
|
|
using TextStyle = Unity.UIWidgets.painting.TextStyle;
|
|
|
|
namespace Unity.UIWidgets.cupertino {
|
|
class NavBarUtils {
|
|
public const float _kNavBarPersistentHeight = 44.0f;
|
|
|
|
public const float _kNavBarLargeTitleHeightExtension = 52.0f;
|
|
|
|
public const float _kNavBarShowLargeTitleThreshold = 10.0f;
|
|
|
|
public const float _kNavBarEdgePadding = 16.0f;
|
|
|
|
public const float _kNavBarBackButtonTapWidth = 50.0f;
|
|
|
|
public static readonly TimeSpan _kNavBarTitleFadeDuration = new TimeSpan(0, 0, 0, 0, 150);
|
|
|
|
public static readonly Color _kDefaultNavBarBorderColor = new Color(0x4C000000);
|
|
|
|
public static readonly Border _kDefaultNavBarBorder = new Border(
|
|
bottom: new BorderSide(
|
|
color: _kDefaultNavBarBorderColor,
|
|
width: 0.0f, // One physical pixel.
|
|
style: BorderStyle.solid
|
|
)
|
|
);
|
|
|
|
public static readonly _HeroTag _defaultHeroTag = new _HeroTag(null);
|
|
|
|
public static Widget _wrapWithBackground(
|
|
Border border = null,
|
|
Color backgroundColor = null,
|
|
Widget child = null,
|
|
bool updateSystemUiOverlay = true
|
|
) {
|
|
Widget result = child;
|
|
if (updateSystemUiOverlay) {
|
|
bool darkBackground = backgroundColor.computeLuminance() < 0.179f;
|
|
SystemUiOverlayStyle overlayStyle = darkBackground
|
|
? SystemUiOverlayStyle.light
|
|
: SystemUiOverlayStyle.dark;
|
|
result = new AnnotatedRegion<SystemUiOverlayStyle>(
|
|
value: overlayStyle,
|
|
sized: true,
|
|
child: result
|
|
);
|
|
}
|
|
|
|
DecoratedBox childWithBackground = new DecoratedBox(
|
|
decoration: new BoxDecoration(
|
|
border: border,
|
|
color: backgroundColor
|
|
),
|
|
child: result
|
|
);
|
|
if (backgroundColor.alpha == 0xFF) {
|
|
return childWithBackground;
|
|
}
|
|
|
|
return new ClipRect(
|
|
child: new BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10.0f, sigmaY: 10.0f),
|
|
child: childWithBackground
|
|
)
|
|
);
|
|
}
|
|
|
|
public static Widget _wrapActiveColor(Color color, BuildContext context, Widget child) {
|
|
if (color == null) {
|
|
return child;
|
|
}
|
|
|
|
return new CupertinoTheme(
|
|
data: CupertinoTheme.of(context).copyWith(primaryColor: color),
|
|
child: child
|
|
);
|
|
}
|
|
|
|
public static bool _isTransitionable(BuildContext context) {
|
|
ModalRoute route = ModalRoute.of(context);
|
|
return route is PageRoute && !(route as PageRoute).fullscreenDialog;
|
|
}
|
|
|
|
public static HeroFlightShuttleBuilder _navBarHeroFlightShuttleBuilder = (
|
|
BuildContext flightContext,
|
|
Animation<float> animation,
|
|
HeroFlightDirection flightDirection,
|
|
BuildContext fromHeroContext,
|
|
BuildContext toHeroContext
|
|
) => {
|
|
D.assert(animation != null);
|
|
|
|
D.assert(fromHeroContext != null);
|
|
D.assert(toHeroContext != null);
|
|
D.assert(fromHeroContext.widget is Hero);
|
|
D.assert(toHeroContext.widget is Hero);
|
|
Hero fromHeroWidget = (Hero) fromHeroContext.widget;
|
|
Hero toHeroWidget = (Hero) toHeroContext.widget;
|
|
D.assert(fromHeroWidget.child is _TransitionableNavigationBar);
|
|
D.assert(toHeroWidget.child is _TransitionableNavigationBar);
|
|
_TransitionableNavigationBar fromNavBar = (_TransitionableNavigationBar) fromHeroWidget.child;
|
|
_TransitionableNavigationBar toNavBar = (_TransitionableNavigationBar) toHeroWidget.child;
|
|
D.assert(fromNavBar.componentsKeys != null);
|
|
D.assert(toNavBar.componentsKeys != null);
|
|
D.assert(
|
|
fromNavBar.componentsKeys.navBarBoxKey.currentContext.owner != null,
|
|
() => "The from nav bar to Hero must have been mounted in the previous frame"
|
|
);
|
|
|
|
D.assert(
|
|
toNavBar.componentsKeys.navBarBoxKey.currentContext.owner != null,
|
|
() => "The to nav bar to Hero must have been mounted in the previous frame"
|
|
);
|
|
|
|
switch (flightDirection) {
|
|
case HeroFlightDirection.push:
|
|
return new _NavigationBarTransition(
|
|
animation: animation,
|
|
bottomNavBar: fromNavBar,
|
|
topNavBar: toNavBar
|
|
);
|
|
case HeroFlightDirection.pop:
|
|
return new _NavigationBarTransition(
|
|
animation: animation,
|
|
bottomNavBar: toNavBar,
|
|
topNavBar: fromNavBar
|
|
);
|
|
}
|
|
|
|
throw new UIWidgetsError($"Unknown flight direction: {flightDirection}");
|
|
};
|
|
|
|
public static CreateRectTween _linearTranslateWithLargestRectSizeTween = (Rect begin, Rect end) => {
|
|
Size largestSize = new Size(
|
|
Mathf.Max(begin.size.width, end.size.width),
|
|
Mathf.Max(begin.size.height, end.size.height)
|
|
);
|
|
return new RectTween(
|
|
begin: begin.topLeft & largestSize,
|
|
end: end.topLeft & largestSize
|
|
);
|
|
};
|
|
|
|
public static TransitionBuilder _navBarHeroLaunchPadBuilder = (
|
|
BuildContext context,
|
|
Widget child
|
|
) => {
|
|
D.assert(child is _TransitionableNavigationBar);
|
|
return new Visibility(
|
|
maintainSize: true,
|
|
maintainAnimation: true,
|
|
maintainState: true,
|
|
visible: false,
|
|
child: child
|
|
);
|
|
};
|
|
}
|
|
|
|
|
|
class _HeroTag {
|
|
public _HeroTag(
|
|
NavigatorState navigator
|
|
) {
|
|
this.navigator = navigator;
|
|
}
|
|
|
|
public readonly NavigatorState navigator;
|
|
|
|
public override string ToString() {
|
|
return $"Default Hero tag for Cupertino navigation bars with navigator {navigator}";
|
|
}
|
|
|
|
public bool Equals(_HeroTag other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return navigator == other.navigator;
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((_HeroTag) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
return navigator.GetHashCode();
|
|
}
|
|
|
|
public static bool operator ==(_HeroTag left, _HeroTag right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(_HeroTag left, _HeroTag right) {
|
|
return !Equals(left, right);
|
|
}
|
|
}
|
|
|
|
public class CupertinoNavigationBar : ObstructingPreferredSizeWidget {
|
|
public CupertinoNavigationBar(
|
|
Key key = null,
|
|
Widget leading = null,
|
|
bool automaticallyImplyLeading = true,
|
|
bool automaticallyImplyMiddle = true,
|
|
string previousPageTitle = null,
|
|
Widget middle = null,
|
|
Widget trailing = null,
|
|
Border border = null,
|
|
Color backgroundColor = null,
|
|
EdgeInsets padding = null,
|
|
Color actionsForegroundColor = null,
|
|
bool transitionBetweenRoutes = true,
|
|
object heroTag = null
|
|
) : base(key: key) {
|
|
this.leading = leading;
|
|
this.automaticallyImplyLeading = automaticallyImplyLeading;
|
|
this.automaticallyImplyMiddle = automaticallyImplyMiddle;
|
|
this.previousPageTitle = previousPageTitle;
|
|
this.middle = middle;
|
|
this.trailing = trailing;
|
|
this.border = border ?? NavBarUtils._kDefaultNavBarBorder;
|
|
this.backgroundColor = backgroundColor;
|
|
this.padding = padding;
|
|
this.actionsForegroundColor = actionsForegroundColor;
|
|
this.transitionBetweenRoutes = transitionBetweenRoutes;
|
|
this.heroTag = heroTag ?? NavBarUtils._defaultHeroTag;
|
|
|
|
D.assert(
|
|
this.heroTag != null,
|
|
() => "heroTag cannot be null. Use transitionBetweenRoutes = false to " +
|
|
"disable Hero transition on this navigation bar."
|
|
);
|
|
|
|
D.assert(
|
|
!transitionBetweenRoutes || ReferenceEquals(this.heroTag, NavBarUtils._defaultHeroTag),
|
|
() => "Cannot specify a heroTag override if this navigation bar does not " +
|
|
"transition due to transitionBetweenRoutes = false."
|
|
);
|
|
}
|
|
|
|
public readonly Widget leading;
|
|
|
|
public readonly bool automaticallyImplyLeading;
|
|
|
|
public readonly bool automaticallyImplyMiddle;
|
|
|
|
public readonly string previousPageTitle;
|
|
|
|
public readonly Widget middle;
|
|
|
|
public readonly Widget trailing;
|
|
|
|
|
|
public readonly Color backgroundColor;
|
|
|
|
public readonly EdgeInsets padding;
|
|
|
|
public readonly Border border;
|
|
|
|
public readonly Color actionsForegroundColor;
|
|
|
|
public readonly bool transitionBetweenRoutes;
|
|
|
|
public readonly object heroTag;
|
|
|
|
public override bool? fullObstruction {
|
|
get { return backgroundColor == null ? null : (bool?) (backgroundColor.alpha == 0xFF); }
|
|
}
|
|
|
|
public override Size preferredSize {
|
|
get { return Size.fromHeight(NavBarUtils._kNavBarPersistentHeight); }
|
|
}
|
|
|
|
public override State createState() {
|
|
return new _CupertinoNavigationBarState();
|
|
}
|
|
}
|
|
|
|
class _CupertinoNavigationBarState : State<CupertinoNavigationBar> {
|
|
_NavigationBarStaticComponentsKeys keys;
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
keys = new _NavigationBarStaticComponentsKeys();
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Color backgroundColor = widget.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor;
|
|
|
|
_NavigationBarStaticComponents components = new _NavigationBarStaticComponents(
|
|
keys: keys,
|
|
route: ModalRoute.of(context),
|
|
userLeading: widget.leading,
|
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
|
automaticallyImplyTitle: widget.automaticallyImplyMiddle,
|
|
previousPageTitle: widget.previousPageTitle,
|
|
userMiddle: widget.middle,
|
|
userTrailing: widget.trailing,
|
|
padding: widget.padding,
|
|
userLargeTitle: null,
|
|
large: false
|
|
);
|
|
|
|
Widget navBar = NavBarUtils._wrapWithBackground(
|
|
border: widget.border,
|
|
backgroundColor: backgroundColor,
|
|
child: new DefaultTextStyle(
|
|
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
child: new _PersistentNavigationBar(
|
|
components: components,
|
|
padding: widget.padding
|
|
)
|
|
)
|
|
);
|
|
|
|
if (!widget.transitionBetweenRoutes || !NavBarUtils._isTransitionable(context)) {
|
|
return NavBarUtils._wrapActiveColor(widget.actionsForegroundColor, context,
|
|
navBar); // ignore: deprecated_member_use_from_same_package
|
|
}
|
|
|
|
return NavBarUtils._wrapActiveColor(
|
|
widget.actionsForegroundColor, // ignore: deprecated_member_use_from_same_package
|
|
context,
|
|
new Builder(
|
|
builder: (BuildContext _context) => {
|
|
return new Hero(
|
|
tag: widget.heroTag as _HeroTag == NavBarUtils._defaultHeroTag
|
|
? new _HeroTag(Navigator.of(_context))
|
|
: widget.heroTag,
|
|
createRectTween: NavBarUtils._linearTranslateWithLargestRectSizeTween,
|
|
placeholderBuilder: NavBarUtils._navBarHeroLaunchPadBuilder,
|
|
flightShuttleBuilder: NavBarUtils._navBarHeroFlightShuttleBuilder,
|
|
transitionOnUserGestures: true,
|
|
child: new _TransitionableNavigationBar(
|
|
componentsKeys: keys,
|
|
backgroundColor: backgroundColor,
|
|
backButtonTextStyle: CupertinoTheme.of(_context).textTheme.navActionTextStyle,
|
|
titleTextStyle: CupertinoTheme.of(_context).textTheme.navTitleTextStyle,
|
|
largeTitleTextStyle: null,
|
|
border: widget.border,
|
|
hasUserMiddle: widget.middle != null,
|
|
largeExpanded: false,
|
|
child: navBar
|
|
)
|
|
);
|
|
}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public class CupertinoSliverNavigationBar : StatefulWidget {
|
|
public CupertinoSliverNavigationBar(
|
|
Key key = null,
|
|
Widget largeTitle = null,
|
|
Widget leading = null,
|
|
bool automaticallyImplyLeading = true,
|
|
bool automaticallyImplyTitle = true,
|
|
string previousPageTitle = null,
|
|
Widget middle = null,
|
|
Widget trailing = null,
|
|
Border border = null,
|
|
Color backgroundColor = null,
|
|
EdgeInsets padding = null,
|
|
Color actionsForegroundColor = null,
|
|
bool transitionBetweenRoutes = true,
|
|
object heroTag = null
|
|
) : base(key: key) {
|
|
D.assert(
|
|
automaticallyImplyTitle == true || largeTitle != null,
|
|
() => "No largeTitle has been provided but automaticallyImplyTitle is also " +
|
|
"false. Either provide a largeTitle or set automaticallyImplyTitle to " +
|
|
"true."
|
|
);
|
|
this.largeTitle = largeTitle;
|
|
this.leading = leading;
|
|
this.automaticallyImplyLeading = automaticallyImplyLeading;
|
|
this.automaticallyImplyTitle = automaticallyImplyTitle;
|
|
this.previousPageTitle = previousPageTitle;
|
|
this.middle = middle;
|
|
this.trailing = trailing;
|
|
this.border = border ?? NavBarUtils._kDefaultNavBarBorder;
|
|
this.backgroundColor = backgroundColor;
|
|
this.padding = padding;
|
|
this.actionsForegroundColor = actionsForegroundColor;
|
|
this.transitionBetweenRoutes = transitionBetweenRoutes;
|
|
this.heroTag = heroTag ?? NavBarUtils._defaultHeroTag;
|
|
}
|
|
|
|
public readonly Widget largeTitle;
|
|
|
|
public readonly Widget leading;
|
|
|
|
public readonly bool automaticallyImplyLeading;
|
|
|
|
public readonly bool automaticallyImplyTitle;
|
|
|
|
public readonly string previousPageTitle;
|
|
|
|
public readonly Widget middle;
|
|
|
|
public readonly Widget trailing;
|
|
|
|
public readonly Color backgroundColor;
|
|
|
|
public readonly EdgeInsets padding;
|
|
|
|
public readonly Border border;
|
|
|
|
public readonly Color actionsForegroundColor;
|
|
|
|
public readonly bool transitionBetweenRoutes;
|
|
|
|
public readonly object heroTag;
|
|
|
|
public bool opaque {
|
|
get { return backgroundColor.alpha == 0xFF; }
|
|
}
|
|
|
|
public override State createState() {
|
|
return new _CupertinoSliverNavigationBarState();
|
|
}
|
|
}
|
|
|
|
class _CupertinoSliverNavigationBarState : State<CupertinoSliverNavigationBar> {
|
|
_NavigationBarStaticComponentsKeys keys;
|
|
|
|
public override void initState() {
|
|
base.initState();
|
|
keys = new _NavigationBarStaticComponentsKeys();
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Color actionsForegroundColor =
|
|
widget.actionsForegroundColor ??
|
|
CupertinoTheme.of(context).primaryColor; // ignore: deprecated_member_use_from_same_package
|
|
|
|
_NavigationBarStaticComponents components = new _NavigationBarStaticComponents(
|
|
keys: keys,
|
|
route: ModalRoute.of(context),
|
|
userLeading: widget.leading,
|
|
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
|
automaticallyImplyTitle: widget.automaticallyImplyTitle,
|
|
previousPageTitle: widget.previousPageTitle,
|
|
userMiddle: widget.middle,
|
|
userTrailing: widget.trailing,
|
|
userLargeTitle: widget.largeTitle,
|
|
padding: widget.padding,
|
|
large: true
|
|
);
|
|
|
|
return NavBarUtils._wrapActiveColor(
|
|
widget.actionsForegroundColor, // ignore: deprecated_member_use_from_same_package
|
|
context,
|
|
new SliverPersistentHeader(
|
|
pinned: true, // iOS navigation bars are always pinned.
|
|
del: new _LargeTitleNavigationBarSliverDelegate(
|
|
keys: keys,
|
|
components: components,
|
|
userMiddle: widget.middle,
|
|
backgroundColor: widget.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
|
|
border: widget.border,
|
|
padding: widget.padding,
|
|
actionsForegroundColor: actionsForegroundColor,
|
|
transitionBetweenRoutes: widget.transitionBetweenRoutes,
|
|
heroTag: widget.heroTag,
|
|
persistentHeight: NavBarUtils._kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
|
|
alwaysShowMiddle: widget.middle != null
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _LargeTitleNavigationBarSliverDelegate
|
|
: SliverPersistentHeaderDelegate {
|
|
public _LargeTitleNavigationBarSliverDelegate(
|
|
_NavigationBarStaticComponentsKeys keys,
|
|
_NavigationBarStaticComponents components,
|
|
Widget userMiddle,
|
|
Color backgroundColor,
|
|
Border border,
|
|
EdgeInsets padding,
|
|
Color actionsForegroundColor,
|
|
bool transitionBetweenRoutes,
|
|
object heroTag,
|
|
float persistentHeight,
|
|
bool alwaysShowMiddle
|
|
) {
|
|
this.keys = keys;
|
|
this.components = components;
|
|
this.userMiddle = userMiddle;
|
|
this.backgroundColor = backgroundColor;
|
|
this.border = border;
|
|
this.padding = padding;
|
|
this.actionsForegroundColor = actionsForegroundColor;
|
|
this.transitionBetweenRoutes = transitionBetweenRoutes;
|
|
this.heroTag = heroTag;
|
|
this.persistentHeight = persistentHeight;
|
|
this.alwaysShowMiddle = alwaysShowMiddle;
|
|
}
|
|
|
|
public readonly _NavigationBarStaticComponentsKeys keys;
|
|
public readonly _NavigationBarStaticComponents components;
|
|
public readonly Widget userMiddle;
|
|
public readonly Color backgroundColor;
|
|
public readonly Border border;
|
|
public readonly EdgeInsets padding;
|
|
public readonly Color actionsForegroundColor;
|
|
public readonly bool transitionBetweenRoutes;
|
|
public readonly object heroTag;
|
|
public readonly float persistentHeight;
|
|
public readonly bool alwaysShowMiddle;
|
|
|
|
public override float? minExtent {
|
|
get { return persistentHeight; }
|
|
}
|
|
|
|
public override float? maxExtent {
|
|
get { return persistentHeight + NavBarUtils._kNavBarLargeTitleHeightExtension; }
|
|
}
|
|
|
|
public override Widget build(BuildContext context, float shrinkOffset, bool overlapsContent) {
|
|
bool showLargeTitle =
|
|
shrinkOffset < maxExtent - minExtent - NavBarUtils._kNavBarShowLargeTitleThreshold;
|
|
|
|
_PersistentNavigationBar persistentNavigationBar =
|
|
new _PersistentNavigationBar(
|
|
components: components,
|
|
padding: padding,
|
|
middleVisible: alwaysShowMiddle ? null : (bool?) !showLargeTitle
|
|
);
|
|
|
|
Widget navBar = NavBarUtils._wrapWithBackground(
|
|
border: border,
|
|
backgroundColor: backgroundColor,
|
|
child: new DefaultTextStyle(
|
|
style: CupertinoTheme.of(context).textTheme.textStyle,
|
|
child: new Stack(
|
|
fit: StackFit.expand,
|
|
children: new List<Widget> {
|
|
new Positioned(
|
|
top: persistentHeight,
|
|
left: 0.0f,
|
|
right: 0.0f,
|
|
bottom: 0.0f,
|
|
child: new ClipRect(
|
|
child: new OverflowBox(
|
|
minHeight: 0.0f,
|
|
maxHeight: float.PositiveInfinity,
|
|
alignment: Alignment.bottomLeft,
|
|
child: new Padding(
|
|
padding: EdgeInsets.only(
|
|
left: NavBarUtils._kNavBarEdgePadding,
|
|
bottom: 8.0f
|
|
),
|
|
child: new SafeArea(
|
|
top: false,
|
|
bottom: false,
|
|
child: new AnimatedOpacity(
|
|
opacity: showLargeTitle ? 1.0f : 0.0f,
|
|
duration: NavBarUtils._kNavBarTitleFadeDuration,
|
|
child: new DefaultTextStyle(
|
|
style: CupertinoTheme.of(context).textTheme
|
|
.navLargeTitleTextStyle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
child: components.largeTitle
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
),
|
|
new Positioned(
|
|
left: 0.0f,
|
|
right: 0.0f,
|
|
top: 0.0f,
|
|
child: persistentNavigationBar
|
|
)
|
|
}
|
|
)
|
|
)
|
|
);
|
|
|
|
if (!transitionBetweenRoutes || !NavBarUtils._isTransitionable(context)) {
|
|
return navBar;
|
|
}
|
|
|
|
return new Hero(
|
|
tag: heroTag as _HeroTag == NavBarUtils._defaultHeroTag
|
|
? new _HeroTag(Navigator.of(context))
|
|
: heroTag,
|
|
createRectTween: NavBarUtils._linearTranslateWithLargestRectSizeTween,
|
|
flightShuttleBuilder: NavBarUtils._navBarHeroFlightShuttleBuilder,
|
|
placeholderBuilder: NavBarUtils._navBarHeroLaunchPadBuilder,
|
|
transitionOnUserGestures: true,
|
|
child: new _TransitionableNavigationBar(
|
|
componentsKeys: keys,
|
|
backgroundColor: backgroundColor,
|
|
backButtonTextStyle: CupertinoTheme.of(context).textTheme.navActionTextStyle,
|
|
titleTextStyle: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
|
largeTitleTextStyle: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
|
|
border: border,
|
|
hasUserMiddle: userMiddle != null,
|
|
largeExpanded: showLargeTitle,
|
|
child: navBar
|
|
)
|
|
);
|
|
}
|
|
|
|
public override bool shouldRebuild(SliverPersistentHeaderDelegate _oldDelegate) {
|
|
_LargeTitleNavigationBarSliverDelegate oldDelegate = _oldDelegate as _LargeTitleNavigationBarSliverDelegate;
|
|
return components != oldDelegate.components
|
|
|| userMiddle != oldDelegate.userMiddle
|
|
|| backgroundColor != oldDelegate.backgroundColor
|
|
|| border != oldDelegate.border
|
|
|| padding != oldDelegate.padding
|
|
|| actionsForegroundColor != oldDelegate.actionsForegroundColor
|
|
|| transitionBetweenRoutes != oldDelegate.transitionBetweenRoutes
|
|
|| persistentHeight != oldDelegate.persistentHeight
|
|
|| alwaysShowMiddle != oldDelegate.alwaysShowMiddle
|
|
|| heroTag != oldDelegate.heroTag;
|
|
}
|
|
}
|
|
|
|
class _PersistentNavigationBar : StatelessWidget {
|
|
public _PersistentNavigationBar(
|
|
Key key = null,
|
|
_NavigationBarStaticComponents components = null,
|
|
EdgeInsets padding = null,
|
|
bool? middleVisible = null
|
|
) : base(key: key) {
|
|
this.components = components;
|
|
this.padding = padding;
|
|
this.middleVisible = middleVisible ?? true;
|
|
}
|
|
|
|
public readonly _NavigationBarStaticComponents components;
|
|
|
|
public readonly EdgeInsets padding;
|
|
public readonly bool middleVisible;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
Widget middle = components.middle;
|
|
|
|
if (middle != null) {
|
|
middle = new DefaultTextStyle(
|
|
style: CupertinoTheme.of(context).textTheme.navTitleTextStyle,
|
|
child: middle
|
|
);
|
|
middle = new AnimatedOpacity(
|
|
opacity: middleVisible ? 1.0f : 0.0f,
|
|
duration: NavBarUtils._kNavBarTitleFadeDuration,
|
|
child: middle
|
|
);
|
|
}
|
|
|
|
Widget leading = components.leading;
|
|
Widget backChevron = components.backChevron;
|
|
Widget backLabel = components.backLabel;
|
|
|
|
if (leading == null && backChevron != null && backLabel != null) {
|
|
leading = CupertinoNavigationBarBackButton._assemble(
|
|
backChevron,
|
|
backLabel
|
|
);
|
|
}
|
|
|
|
Widget paddedToolbar = new NavigationToolbar(
|
|
leading: leading,
|
|
middle: middle,
|
|
trailing: components.trailing,
|
|
centerMiddle: true,
|
|
middleSpacing: 6.0f
|
|
);
|
|
|
|
if (padding != null) {
|
|
paddedToolbar = new Padding(
|
|
padding: EdgeInsets.only(
|
|
top: padding.top,
|
|
bottom: padding.bottom
|
|
),
|
|
child: paddedToolbar
|
|
);
|
|
}
|
|
|
|
return new SizedBox(
|
|
height: NavBarUtils._kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
|
|
child: new SafeArea(
|
|
bottom: false,
|
|
child: paddedToolbar
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationBarStaticComponentsKeys {
|
|
public _NavigationBarStaticComponentsKeys() {
|
|
navBarBoxKey = GlobalKey.key(debugLabel: "Navigation bar render box");
|
|
leadingKey = GlobalKey.key(debugLabel: "Leading");
|
|
backChevronKey = GlobalKey.key(debugLabel: "Back chevron");
|
|
backLabelKey = GlobalKey.key(debugLabel: "Back label");
|
|
middleKey = GlobalKey.key(debugLabel: "Middle");
|
|
trailingKey = GlobalKey.key(debugLabel: "Trailing");
|
|
largeTitleKey = GlobalKey.key(debugLabel: "Large title");
|
|
}
|
|
|
|
public readonly GlobalKey navBarBoxKey;
|
|
public readonly GlobalKey leadingKey;
|
|
public readonly GlobalKey backChevronKey;
|
|
public readonly GlobalKey backLabelKey;
|
|
public readonly GlobalKey middleKey;
|
|
public readonly GlobalKey trailingKey;
|
|
public readonly GlobalKey largeTitleKey;
|
|
}
|
|
|
|
class _NavigationBarStaticComponents {
|
|
public _NavigationBarStaticComponents(
|
|
_NavigationBarStaticComponentsKeys keys,
|
|
ModalRoute route,
|
|
Widget userLeading,
|
|
bool automaticallyImplyLeading,
|
|
bool automaticallyImplyTitle,
|
|
string previousPageTitle,
|
|
Widget userMiddle,
|
|
Widget userTrailing,
|
|
Widget userLargeTitle,
|
|
EdgeInsets padding,
|
|
bool large
|
|
) {
|
|
leading = createLeading(
|
|
leadingKey: keys.leadingKey,
|
|
userLeading: userLeading,
|
|
route: route,
|
|
automaticallyImplyLeading: automaticallyImplyLeading,
|
|
padding: padding
|
|
);
|
|
backChevron = createBackChevron(
|
|
backChevronKey: keys.backChevronKey,
|
|
userLeading: userLeading,
|
|
route: route,
|
|
automaticallyImplyLeading: automaticallyImplyLeading
|
|
);
|
|
backLabel = createBackLabel(
|
|
backLabelKey: keys.backLabelKey,
|
|
userLeading: userLeading,
|
|
route: route,
|
|
previousPageTitle: previousPageTitle,
|
|
automaticallyImplyLeading: automaticallyImplyLeading
|
|
);
|
|
middle = createMiddle(
|
|
middleKey: keys.middleKey,
|
|
userMiddle: userMiddle,
|
|
userLargeTitle: userLargeTitle,
|
|
route: route,
|
|
automaticallyImplyTitle: automaticallyImplyTitle,
|
|
large: large
|
|
);
|
|
trailing = createTrailing(
|
|
trailingKey: keys.trailingKey,
|
|
userTrailing: userTrailing,
|
|
padding: padding
|
|
);
|
|
largeTitle = createLargeTitle(
|
|
largeTitleKey: keys.largeTitleKey,
|
|
userLargeTitle: userLargeTitle,
|
|
route: route,
|
|
automaticImplyTitle: automaticallyImplyTitle,
|
|
large: large
|
|
);
|
|
}
|
|
|
|
static Widget _derivedTitle(
|
|
bool automaticallyImplyTitle,
|
|
ModalRoute currentRoute
|
|
) {
|
|
if (automaticallyImplyTitle &&
|
|
currentRoute is CupertinoPageRoute route &&
|
|
route.title != null) {
|
|
return new Text(route.title);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public readonly KeyedSubtree leading;
|
|
|
|
static KeyedSubtree createLeading(
|
|
GlobalKey leadingKey,
|
|
Widget userLeading,
|
|
ModalRoute route,
|
|
bool automaticallyImplyLeading,
|
|
EdgeInsets padding
|
|
) {
|
|
Widget leadingContent = null;
|
|
|
|
if (userLeading != null) {
|
|
leadingContent = userLeading;
|
|
}
|
|
else if (
|
|
automaticallyImplyLeading &&
|
|
route is PageRoute pageRoute &&
|
|
route.canPop &&
|
|
pageRoute.fullscreenDialog
|
|
) {
|
|
leadingContent = new CupertinoButton(
|
|
child: new Text("Close"),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: () => { route.navigator.maybePop(); }
|
|
);
|
|
}
|
|
|
|
if (leadingContent == null) {
|
|
return null;
|
|
}
|
|
|
|
return new KeyedSubtree(
|
|
key: leadingKey,
|
|
child: new Padding(
|
|
padding: EdgeInsets.only(
|
|
left: padding?.left ?? NavBarUtils._kNavBarEdgePadding
|
|
),
|
|
child: IconTheme.merge(
|
|
data: new IconThemeData(
|
|
size: 32.0f
|
|
),
|
|
child: leadingContent
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public readonly KeyedSubtree backChevron;
|
|
|
|
static KeyedSubtree createBackChevron(
|
|
GlobalKey backChevronKey,
|
|
Widget userLeading,
|
|
ModalRoute route,
|
|
bool automaticallyImplyLeading
|
|
) {
|
|
if (
|
|
userLeading != null ||
|
|
!automaticallyImplyLeading ||
|
|
route == null ||
|
|
!route.canPop ||
|
|
(route is PageRoute pageRoute && pageRoute.fullscreenDialog)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return new KeyedSubtree(key: backChevronKey, child: new _BackChevron());
|
|
}
|
|
|
|
public readonly KeyedSubtree backLabel;
|
|
|
|
static KeyedSubtree createBackLabel(
|
|
GlobalKey backLabelKey,
|
|
Widget userLeading,
|
|
ModalRoute route,
|
|
bool automaticallyImplyLeading,
|
|
string previousPageTitle
|
|
) {
|
|
if (
|
|
userLeading != null ||
|
|
!automaticallyImplyLeading ||
|
|
route == null ||
|
|
!route.canPop ||
|
|
(route is PageRoute pageRoute && pageRoute.fullscreenDialog)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return new KeyedSubtree(
|
|
key: backLabelKey,
|
|
child: new _BackLabel(
|
|
specifiedPreviousTitle: previousPageTitle,
|
|
route: route
|
|
)
|
|
);
|
|
}
|
|
|
|
public readonly KeyedSubtree middle;
|
|
|
|
static KeyedSubtree createMiddle(
|
|
GlobalKey middleKey,
|
|
Widget userMiddle,
|
|
Widget userLargeTitle,
|
|
bool large,
|
|
bool automaticallyImplyTitle,
|
|
ModalRoute route
|
|
) {
|
|
Widget middleContent = userMiddle;
|
|
|
|
if (large) {
|
|
middleContent = middleContent ?? userLargeTitle;
|
|
}
|
|
|
|
middleContent = middleContent ?? _derivedTitle(
|
|
automaticallyImplyTitle: automaticallyImplyTitle,
|
|
currentRoute: route
|
|
);
|
|
|
|
if (middleContent == null) {
|
|
return null;
|
|
}
|
|
|
|
return new KeyedSubtree(
|
|
key: middleKey,
|
|
child: middleContent
|
|
);
|
|
}
|
|
|
|
public readonly KeyedSubtree trailing;
|
|
|
|
static KeyedSubtree createTrailing(
|
|
GlobalKey trailingKey,
|
|
Widget userTrailing,
|
|
EdgeInsets padding
|
|
) {
|
|
if (userTrailing == null) {
|
|
return null;
|
|
}
|
|
|
|
return new KeyedSubtree(
|
|
key: trailingKey,
|
|
child: new Padding(
|
|
padding: EdgeInsets.only(
|
|
right: padding?.right ?? NavBarUtils._kNavBarEdgePadding
|
|
),
|
|
child: IconTheme.merge(
|
|
data: new IconThemeData(
|
|
size: 32.0f
|
|
),
|
|
child: userTrailing
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
public readonly KeyedSubtree largeTitle;
|
|
|
|
static KeyedSubtree createLargeTitle(
|
|
GlobalKey largeTitleKey,
|
|
Widget userLargeTitle,
|
|
bool large,
|
|
bool automaticImplyTitle,
|
|
ModalRoute route
|
|
) {
|
|
if (!large) {
|
|
return null;
|
|
}
|
|
|
|
Widget largeTitleContent = userLargeTitle ?? _derivedTitle(
|
|
automaticallyImplyTitle: automaticImplyTitle,
|
|
currentRoute: route
|
|
);
|
|
|
|
D.assert(
|
|
largeTitleContent != null,
|
|
() => "largeTitle was not provided and there was no title from the route."
|
|
);
|
|
|
|
return new KeyedSubtree(
|
|
key: largeTitleKey,
|
|
child: largeTitleContent
|
|
);
|
|
}
|
|
}
|
|
|
|
public class CupertinoNavigationBarBackButton : StatelessWidget {
|
|
public CupertinoNavigationBarBackButton(
|
|
Color color,
|
|
string previousPageTitle
|
|
) {
|
|
_backChevron = null;
|
|
_backLabel = null;
|
|
this.color = color;
|
|
this.previousPageTitle = previousPageTitle;
|
|
}
|
|
|
|
internal CupertinoNavigationBarBackButton(
|
|
Color color,
|
|
string previousPageTitle,
|
|
Widget backChevron,
|
|
Widget backLabel
|
|
) {
|
|
_backChevron = backChevron;
|
|
_backLabel = backLabel;
|
|
this.color = color;
|
|
this.previousPageTitle = previousPageTitle;
|
|
}
|
|
|
|
public static CupertinoNavigationBarBackButton _assemble(
|
|
Widget _backChevron,
|
|
Widget _backLabel
|
|
) {
|
|
return new CupertinoNavigationBarBackButton(
|
|
backChevron: _backChevron,
|
|
backLabel: _backLabel,
|
|
color: null,
|
|
previousPageTitle: null
|
|
);
|
|
}
|
|
|
|
public readonly Color color;
|
|
|
|
public readonly string previousPageTitle;
|
|
|
|
public readonly Widget _backChevron;
|
|
|
|
public readonly Widget _backLabel;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
ModalRoute currentRoute = ModalRoute.of(context);
|
|
D.assert(
|
|
currentRoute?.canPop == true,
|
|
() => "CupertinoNavigationBarBackButton should only be used in routes that can be popped"
|
|
);
|
|
|
|
TextStyle actionTextStyle = CupertinoTheme.of(context).textTheme.navActionTextStyle;
|
|
if (color != null) {
|
|
actionTextStyle = actionTextStyle.copyWith(color: color);
|
|
}
|
|
|
|
return new CupertinoButton(
|
|
child: new DefaultTextStyle(
|
|
style: actionTextStyle,
|
|
child: new ConstrainedBox(
|
|
constraints: new BoxConstraints(minWidth: NavBarUtils._kNavBarBackButtonTapWidth),
|
|
child: new Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.start,
|
|
children: new List<Widget> {
|
|
new Padding(padding: EdgeInsets.only(left: 8.0f)),
|
|
_backChevron ?? new _BackChevron(),
|
|
new Padding(padding: EdgeInsets.only(left: 6.0f)),
|
|
new Flexible(
|
|
child: _backLabel ?? new _BackLabel(
|
|
specifiedPreviousTitle: previousPageTitle,
|
|
route: currentRoute
|
|
)
|
|
)
|
|
}
|
|
)
|
|
)
|
|
),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: () => { Navigator.maybePop(context); }
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
class _BackChevron : StatelessWidget {
|
|
public _BackChevron(Key key = null) : base(key: key) { }
|
|
|
|
public override Widget build(BuildContext context) {
|
|
TextStyle textStyle = DefaultTextStyle.of(context).style;
|
|
|
|
Widget iconWidget = Text.rich(
|
|
new TextSpan(
|
|
text: char.ConvertFromUtf32(CupertinoIcons.back.codePoint),
|
|
style: new TextStyle(
|
|
inherit: false,
|
|
color: textStyle.color,
|
|
fontSize: 34.0f,
|
|
fontFamily: CupertinoIcons.back.fontFamily
|
|
)
|
|
)
|
|
);
|
|
|
|
return iconWidget;
|
|
}
|
|
}
|
|
|
|
class _BackLabel : StatelessWidget {
|
|
public _BackLabel(
|
|
Key key = null,
|
|
string specifiedPreviousTitle = null,
|
|
ModalRoute route = null
|
|
) : base(key: key) {
|
|
D.assert(route != null);
|
|
this.specifiedPreviousTitle = specifiedPreviousTitle;
|
|
this.route = route;
|
|
}
|
|
|
|
public readonly string specifiedPreviousTitle;
|
|
public readonly ModalRoute route;
|
|
|
|
Widget _buildPreviousTitleWidget(BuildContext context, string previousTitle, Widget child) {
|
|
if (previousTitle == null) {
|
|
return new SizedBox(height: 0.0f, width: 0.0f);
|
|
}
|
|
|
|
Text textWidget = new Text(
|
|
previousTitle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis
|
|
);
|
|
|
|
if (previousTitle.Length > 12) {
|
|
textWidget = new Text("Back");
|
|
}
|
|
|
|
return new Align(
|
|
alignment: Alignment.centerLeft,
|
|
widthFactor: 1.0f,
|
|
child: textWidget
|
|
);
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
if (specifiedPreviousTitle != null) {
|
|
return _buildPreviousTitleWidget(context, specifiedPreviousTitle, null);
|
|
}
|
|
else if (route is CupertinoPageRoute cupertinoRoute) {
|
|
return new ValueListenableBuilder<string>(
|
|
valueListenable: cupertinoRoute.previousTitle,
|
|
builder: _buildPreviousTitleWidget
|
|
);
|
|
}
|
|
else {
|
|
return new SizedBox(height: 0.0f, width: 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _TransitionableNavigationBar : StatelessWidget {
|
|
public _TransitionableNavigationBar(
|
|
_NavigationBarStaticComponentsKeys componentsKeys = null,
|
|
Color backgroundColor = null,
|
|
TextStyle backButtonTextStyle = null,
|
|
TextStyle titleTextStyle = null,
|
|
TextStyle largeTitleTextStyle = null,
|
|
Border border = null,
|
|
bool? hasUserMiddle = null,
|
|
bool? largeExpanded = null,
|
|
Widget child = null
|
|
) : base(key: componentsKeys.navBarBoxKey) {
|
|
D.assert(largeExpanded != null);
|
|
D.assert(!largeExpanded.Value || largeTitleTextStyle != null);
|
|
|
|
this.componentsKeys = componentsKeys;
|
|
this.backgroundColor = backgroundColor;
|
|
this.backButtonTextStyle = backButtonTextStyle;
|
|
this.titleTextStyle = titleTextStyle;
|
|
this.largeTitleTextStyle = largeTitleTextStyle;
|
|
this.border = border;
|
|
this.hasUserMiddle = hasUserMiddle;
|
|
this.largeExpanded = largeExpanded;
|
|
this.child = child;
|
|
}
|
|
|
|
public readonly _NavigationBarStaticComponentsKeys componentsKeys;
|
|
public readonly Color backgroundColor;
|
|
public readonly TextStyle backButtonTextStyle;
|
|
public readonly TextStyle titleTextStyle;
|
|
public readonly TextStyle largeTitleTextStyle;
|
|
public readonly Border border;
|
|
public readonly bool? hasUserMiddle;
|
|
public readonly bool? largeExpanded;
|
|
public readonly Widget child;
|
|
|
|
public RenderBox renderBox {
|
|
get {
|
|
RenderBox box = (RenderBox) componentsKeys.navBarBoxKey.currentContext.findRenderObject();
|
|
D.assert(
|
|
box.attached,
|
|
() => "_TransitionableNavigationBar.renderBox should be called when building " +
|
|
"hero flight shuttles when the from and the to nav bar boxes are already " +
|
|
"laid out and painted."
|
|
);
|
|
return box;
|
|
}
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(() => {
|
|
bool? inHero = null;
|
|
context.visitAncestorElements((Element ancestor) => {
|
|
if (ancestor is ComponentElement) {
|
|
D.assert(
|
|
ancestor.widget.GetType() != typeof(_NavigationBarTransition),
|
|
() => "_TransitionableNavigationBar should never re-appear inside " +
|
|
"_NavigationBarTransition. Keyed _TransitionableNavigationBar should " +
|
|
"only serve as anchor points in routes rather than appearing inside " +
|
|
"Hero flights themselves."
|
|
);
|
|
if (ancestor.widget.GetType() == typeof(Hero)) {
|
|
inHero = true;
|
|
}
|
|
}
|
|
|
|
inHero = inHero ?? false;
|
|
return true;
|
|
});
|
|
D.assert(
|
|
inHero == true,
|
|
() => "_TransitionableNavigationBar should only be added as the immediate " +
|
|
"child of Hero widgets."
|
|
);
|
|
return true;
|
|
});
|
|
return child;
|
|
}
|
|
}
|
|
|
|
class _NavigationBarTransition : StatelessWidget {
|
|
public _NavigationBarTransition(
|
|
Animation<float> animation,
|
|
_TransitionableNavigationBar topNavBar,
|
|
_TransitionableNavigationBar bottomNavBar
|
|
) {
|
|
this.animation = animation;
|
|
this.topNavBar = topNavBar;
|
|
this.bottomNavBar = bottomNavBar;
|
|
heightTween = new FloatTween(
|
|
begin: this.bottomNavBar.renderBox.size.height,
|
|
end: this.topNavBar.renderBox.size.height
|
|
);
|
|
backgroundTween = new ColorTween(
|
|
begin: this.bottomNavBar.backgroundColor,
|
|
end: this.topNavBar.backgroundColor
|
|
);
|
|
borderTween = new BorderTween(
|
|
begin: this.bottomNavBar.border,
|
|
end: this.topNavBar.border
|
|
);
|
|
}
|
|
|
|
public readonly Animation<float> animation;
|
|
public readonly _TransitionableNavigationBar topNavBar;
|
|
public readonly _TransitionableNavigationBar bottomNavBar;
|
|
|
|
public readonly FloatTween heightTween;
|
|
public readonly ColorTween backgroundTween;
|
|
public readonly BorderTween borderTween;
|
|
|
|
public override Widget build(BuildContext context) {
|
|
_NavigationBarComponentsTransition componentsTransition = new _NavigationBarComponentsTransition(
|
|
animation: animation,
|
|
bottomNavBar: bottomNavBar,
|
|
topNavBar: topNavBar,
|
|
directionality: Directionality.of(context)
|
|
);
|
|
|
|
List<Widget> children = new List<Widget> {
|
|
new AnimatedBuilder(
|
|
animation: animation,
|
|
builder: (BuildContext _context, Widget child) => {
|
|
return NavBarUtils._wrapWithBackground(
|
|
updateSystemUiOverlay: false,
|
|
backgroundColor: backgroundTween.evaluate(animation),
|
|
border: borderTween.evaluate(animation),
|
|
child: new SizedBox(
|
|
height: heightTween.evaluate(animation),
|
|
width: float.PositiveInfinity
|
|
)
|
|
);
|
|
}
|
|
),
|
|
componentsTransition.bottomBackChevron,
|
|
componentsTransition.bottomBackLabel,
|
|
componentsTransition.bottomLeading,
|
|
componentsTransition.bottomMiddle,
|
|
componentsTransition.bottomLargeTitle,
|
|
componentsTransition.bottomTrailing,
|
|
componentsTransition.topLeading,
|
|
componentsTransition.topBackChevron,
|
|
componentsTransition.topBackLabel,
|
|
componentsTransition.topMiddle,
|
|
componentsTransition.topLargeTitle,
|
|
componentsTransition.topTrailing
|
|
};
|
|
|
|
children.RemoveAll((Widget child) => child == null);
|
|
|
|
return new SizedBox(
|
|
height: Mathf.Max(heightTween.begin, heightTween.end) + MediaQuery.of(context).padding.top,
|
|
width: float.PositiveInfinity,
|
|
child: new Stack(
|
|
children: children
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationBarComponentsTransition {
|
|
public _NavigationBarComponentsTransition(
|
|
Animation<float> animation,
|
|
_TransitionableNavigationBar bottomNavBar,
|
|
_TransitionableNavigationBar topNavBar,
|
|
TextDirection directionality
|
|
) {
|
|
this.animation = animation;
|
|
bottomComponents = bottomNavBar.componentsKeys;
|
|
topComponents = topNavBar.componentsKeys;
|
|
bottomNavBarBox = bottomNavBar.renderBox;
|
|
topNavBarBox = topNavBar.renderBox;
|
|
bottomBackButtonTextStyle = bottomNavBar.backButtonTextStyle;
|
|
topBackButtonTextStyle = topNavBar.backButtonTextStyle;
|
|
bottomTitleTextStyle = bottomNavBar.titleTextStyle;
|
|
topTitleTextStyle = topNavBar.titleTextStyle;
|
|
bottomLargeTitleTextStyle = bottomNavBar.largeTitleTextStyle;
|
|
topLargeTitleTextStyle = topNavBar.largeTitleTextStyle;
|
|
bottomHasUserMiddle = bottomNavBar.hasUserMiddle;
|
|
topHasUserMiddle = topNavBar.hasUserMiddle;
|
|
bottomLargeExpanded = bottomNavBar.largeExpanded;
|
|
topLargeExpanded = topNavBar.largeExpanded;
|
|
transitionBox =
|
|
bottomNavBar.renderBox.paintBounds.expandToInclude(topNavBar.renderBox.paintBounds);
|
|
forwardDirection = directionality == TextDirection.ltr ? 1.0f : -1.0f;
|
|
}
|
|
|
|
public static Animatable<float> fadeOut = new FloatTween(
|
|
begin: 1.0f,
|
|
end: 0.0f
|
|
);
|
|
|
|
public static Animatable<float> fadeIn = new FloatTween(
|
|
begin: 0.0f,
|
|
end: 1.0f
|
|
);
|
|
|
|
public readonly Animation<float> animation;
|
|
public readonly _NavigationBarStaticComponentsKeys bottomComponents;
|
|
public readonly _NavigationBarStaticComponentsKeys topComponents;
|
|
|
|
public readonly RenderBox bottomNavBarBox;
|
|
public readonly RenderBox topNavBarBox;
|
|
|
|
public readonly TextStyle bottomBackButtonTextStyle;
|
|
public readonly TextStyle topBackButtonTextStyle;
|
|
public readonly TextStyle bottomTitleTextStyle;
|
|
public readonly TextStyle topTitleTextStyle;
|
|
public readonly TextStyle bottomLargeTitleTextStyle;
|
|
public readonly TextStyle topLargeTitleTextStyle;
|
|
|
|
public readonly bool? bottomHasUserMiddle;
|
|
public readonly bool? topHasUserMiddle;
|
|
public readonly bool? bottomLargeExpanded;
|
|
public readonly bool? topLargeExpanded;
|
|
|
|
public readonly Rect transitionBox;
|
|
|
|
public readonly float forwardDirection;
|
|
|
|
public RelativeRect positionInTransitionBox(
|
|
GlobalKey key,
|
|
RenderBox from
|
|
) {
|
|
RenderBox componentBox = (RenderBox) key.currentContext.findRenderObject();
|
|
D.assert(componentBox.attached);
|
|
|
|
return RelativeRect.fromRect(
|
|
componentBox.localToGlobal(Offset.zero, ancestor: from) & componentBox.size, transitionBox
|
|
);
|
|
}
|
|
|
|
public RelativeRectTween slideFromLeadingEdge(
|
|
GlobalKey fromKey,
|
|
RenderBox fromNavBarBox,
|
|
GlobalKey toKey,
|
|
RenderBox toNavBarBox
|
|
) {
|
|
RelativeRect fromRect = positionInTransitionBox(fromKey, from: fromNavBarBox);
|
|
|
|
RenderBox fromBox = (RenderBox) fromKey.currentContext.findRenderObject();
|
|
RenderBox toBox = (RenderBox) toKey.currentContext.findRenderObject();
|
|
|
|
Rect toRect =
|
|
toBox.localToGlobal(
|
|
Offset.zero,
|
|
ancestor: toNavBarBox
|
|
).translate(
|
|
0.0f,
|
|
-fromBox.size.height / 2 + toBox.size.height / 2
|
|
) & fromBox.size; // Keep the from render object"s size.
|
|
|
|
if (forwardDirection < 0) {
|
|
toRect = toRect.translate(-fromBox.size.width + toBox.size.width, 0.0f);
|
|
}
|
|
|
|
return new RelativeRectTween(
|
|
begin: fromRect,
|
|
end: RelativeRect.fromRect(toRect, transitionBox)
|
|
);
|
|
}
|
|
|
|
public Animation<float> fadeInFrom(float t, Curve curve = null) {
|
|
return animation.drive(fadeIn.chain(
|
|
new CurveTween(curve: new Interval(t, 1.0f, curve: curve ?? Curves.easeIn))
|
|
));
|
|
}
|
|
|
|
public Animation<float> fadeOutBy(float t, Curve curve = null) {
|
|
return animation.drive(fadeOut.chain(
|
|
new CurveTween(curve: new Interval(0.0f, t, curve: curve ?? Curves.easeOut))
|
|
));
|
|
}
|
|
|
|
public Widget bottomLeading {
|
|
get {
|
|
KeyedSubtree bottomLeading = (KeyedSubtree) bottomComponents.leadingKey.currentWidget;
|
|
|
|
if (bottomLeading == null) {
|
|
return null;
|
|
}
|
|
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(bottomComponents.leadingKey, from: bottomNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.4f),
|
|
child: bottomLeading.child
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget bottomBackChevron {
|
|
get {
|
|
KeyedSubtree bottomBackChevron = (KeyedSubtree) bottomComponents.backChevronKey.currentWidget;
|
|
|
|
if (bottomBackChevron == null) {
|
|
return null;
|
|
}
|
|
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(bottomComponents.backChevronKey,
|
|
from: bottomNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.6f),
|
|
child: new DefaultTextStyle(
|
|
style: bottomBackButtonTextStyle,
|
|
child: bottomBackChevron.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget bottomBackLabel {
|
|
get {
|
|
KeyedSubtree bottomBackLabel = (KeyedSubtree) bottomComponents.backLabelKey.currentWidget;
|
|
|
|
if (bottomBackLabel == null) {
|
|
return null;
|
|
}
|
|
|
|
RelativeRect from =
|
|
positionInTransitionBox(bottomComponents.backLabelKey, from: bottomNavBarBox);
|
|
|
|
RelativeRectTween positionTween = new RelativeRectTween(
|
|
begin: from,
|
|
end: from.shift(
|
|
new Offset(forwardDirection * (-bottomNavBarBox.size.width / 2.0f),
|
|
0.0f
|
|
)
|
|
)
|
|
);
|
|
|
|
return new PositionedTransition(
|
|
rect: animation.drive(positionTween),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.2f),
|
|
child: new DefaultTextStyle(
|
|
style: bottomBackButtonTextStyle,
|
|
child: bottomBackLabel.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget bottomMiddle {
|
|
get {
|
|
KeyedSubtree bottomMiddle = (KeyedSubtree) bottomComponents.middleKey.currentWidget;
|
|
KeyedSubtree topBackLabel = (KeyedSubtree) topComponents.backLabelKey.currentWidget;
|
|
KeyedSubtree topLeading = (KeyedSubtree) topComponents.leadingKey.currentWidget;
|
|
|
|
if (bottomHasUserMiddle != true && bottomLargeExpanded == true) {
|
|
return null;
|
|
}
|
|
|
|
if (bottomMiddle != null && topBackLabel != null) {
|
|
return new PositionedTransition(
|
|
rect: animation.drive(slideFromLeadingEdge(
|
|
fromKey: bottomComponents.middleKey,
|
|
fromNavBarBox: bottomNavBarBox,
|
|
toKey: topComponents.backLabelKey,
|
|
toNavBarBox: topNavBarBox
|
|
)),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(bottomHasUserMiddle == true ? 0.4f : 0.7f),
|
|
child: new Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: new DefaultTextStyleTransition(
|
|
style: animation.drive(new TextStyleTween(
|
|
begin: bottomTitleTextStyle,
|
|
end: topBackButtonTextStyle
|
|
)),
|
|
child: bottomMiddle.child
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (bottomMiddle != null && topLeading != null) {
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(bottomComponents.middleKey, from: bottomNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(bottomHasUserMiddle == true ? 0.4f : 0.7f),
|
|
child: new DefaultTextStyle(
|
|
style: bottomTitleTextStyle,
|
|
child: bottomMiddle.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Widget bottomLargeTitle {
|
|
get {
|
|
KeyedSubtree bottomLargeTitle = (KeyedSubtree) bottomComponents.largeTitleKey.currentWidget;
|
|
KeyedSubtree topBackLabel = (KeyedSubtree) topComponents.backLabelKey.currentWidget;
|
|
KeyedSubtree topLeading = (KeyedSubtree) topComponents.leadingKey.currentWidget;
|
|
|
|
if (bottomLargeTitle == null || bottomLargeExpanded != true) {
|
|
return null;
|
|
}
|
|
|
|
if (bottomLargeTitle != null && topBackLabel != null) {
|
|
return new PositionedTransition(
|
|
rect: animation.drive(slideFromLeadingEdge(
|
|
fromKey: bottomComponents.largeTitleKey,
|
|
fromNavBarBox: bottomNavBarBox,
|
|
toKey: topComponents.backLabelKey,
|
|
toNavBarBox: topNavBarBox
|
|
)),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.6f),
|
|
child: new Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: new DefaultTextStyleTransition(
|
|
style: animation.drive(new TextStyleTween(
|
|
begin: bottomLargeTitleTextStyle,
|
|
end: topBackButtonTextStyle
|
|
)),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
child: bottomLargeTitle.child
|
|
)
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (bottomLargeTitle != null && topLeading != null) {
|
|
RelativeRect from = positionInTransitionBox(bottomComponents.largeTitleKey,
|
|
from: bottomNavBarBox);
|
|
|
|
RelativeRectTween positionTween = new RelativeRectTween(
|
|
begin: from,
|
|
end: from.shift(
|
|
new Offset(forwardDirection * bottomNavBarBox.size.width / 4.0f,
|
|
0.0f
|
|
)
|
|
)
|
|
);
|
|
|
|
return new PositionedTransition(
|
|
rect: animation.drive(positionTween),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.4f),
|
|
child: new DefaultTextStyle(
|
|
style: bottomLargeTitleTextStyle,
|
|
child: bottomLargeTitle.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Widget bottomTrailing {
|
|
get {
|
|
KeyedSubtree bottomTrailing = (KeyedSubtree) bottomComponents.trailingKey.currentWidget;
|
|
|
|
if (bottomTrailing == null) {
|
|
return null;
|
|
}
|
|
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(bottomComponents.trailingKey, from: bottomNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeOutBy(0.6f),
|
|
child: bottomTrailing.child
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget topLeading {
|
|
get {
|
|
KeyedSubtree topLeading = (KeyedSubtree) topComponents.leadingKey.currentWidget;
|
|
|
|
if (topLeading == null) {
|
|
return null;
|
|
}
|
|
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(topComponents.leadingKey, from: topNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeInFrom(0.6f),
|
|
child: topLeading.child
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget topBackChevron {
|
|
get {
|
|
KeyedSubtree topBackChevron = (KeyedSubtree) topComponents.backChevronKey.currentWidget;
|
|
KeyedSubtree bottomBackChevron = (KeyedSubtree) bottomComponents.backChevronKey.currentWidget;
|
|
|
|
if (topBackChevron == null) {
|
|
return null;
|
|
}
|
|
|
|
RelativeRect to =
|
|
positionInTransitionBox(topComponents.backChevronKey, from: topNavBarBox);
|
|
RelativeRect from = to;
|
|
|
|
if (bottomBackChevron == null) {
|
|
RenderBox topBackChevronBox =
|
|
(RenderBox) topComponents.backChevronKey.currentContext.findRenderObject();
|
|
from = to.shift(
|
|
new Offset(forwardDirection * topBackChevronBox.size.width * 2.0f,
|
|
0.0f
|
|
)
|
|
);
|
|
}
|
|
|
|
RelativeRectTween positionTween = new RelativeRectTween(
|
|
begin: from,
|
|
end: to
|
|
);
|
|
|
|
return new PositionedTransition(
|
|
rect: animation.drive(positionTween),
|
|
child: new FadeTransition(
|
|
opacity: fadeInFrom(bottomBackChevron == null ? 0.7f : 0.4f),
|
|
child: new DefaultTextStyle(
|
|
style: topBackButtonTextStyle,
|
|
child: topBackChevron.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget topBackLabel {
|
|
get {
|
|
KeyedSubtree bottomMiddle = (KeyedSubtree) bottomComponents.middleKey.currentWidget;
|
|
KeyedSubtree bottomLargeTitle = (KeyedSubtree) bottomComponents.largeTitleKey.currentWidget;
|
|
KeyedSubtree topBackLabel = (KeyedSubtree) topComponents.backLabelKey.currentWidget;
|
|
|
|
if (topBackLabel == null) {
|
|
return null;
|
|
}
|
|
|
|
RenderAnimatedOpacity topBackLabelOpacity =
|
|
(RenderAnimatedOpacity) topComponents.backLabelKey.currentContext?.ancestorRenderObjectOfType(
|
|
new TypeMatcher<RenderAnimatedOpacity>()
|
|
);
|
|
|
|
Animation<float> midClickOpacity = null;
|
|
if (topBackLabelOpacity != null && topBackLabelOpacity.opacity.value < 1.0f) {
|
|
midClickOpacity = animation.drive(new FloatTween(
|
|
begin: 0.0f,
|
|
end: topBackLabelOpacity.opacity.value
|
|
));
|
|
}
|
|
|
|
if (bottomLargeTitle != null &&
|
|
topBackLabel != null && bottomLargeExpanded.Value) {
|
|
return new PositionedTransition(
|
|
rect: animation.drive(slideFromLeadingEdge(
|
|
fromKey: bottomComponents.largeTitleKey,
|
|
fromNavBarBox: bottomNavBarBox,
|
|
toKey: topComponents.backLabelKey,
|
|
toNavBarBox: topNavBarBox
|
|
)),
|
|
child: new FadeTransition(
|
|
opacity: midClickOpacity ?? fadeInFrom(0.4f),
|
|
child: new DefaultTextStyleTransition(
|
|
style: animation.drive(new TextStyleTween(
|
|
begin: bottomLargeTitleTextStyle,
|
|
end: topBackButtonTextStyle
|
|
)),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
child: topBackLabel.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (bottomMiddle != null && topBackLabel != null) {
|
|
return new PositionedTransition(
|
|
rect: animation.drive(slideFromLeadingEdge(
|
|
fromKey: bottomComponents.middleKey,
|
|
fromNavBarBox: bottomNavBarBox,
|
|
toKey: topComponents.backLabelKey,
|
|
toNavBarBox: topNavBarBox
|
|
)),
|
|
child: new FadeTransition(
|
|
opacity: midClickOpacity ?? fadeInFrom(0.3f),
|
|
child: new DefaultTextStyleTransition(
|
|
style: animation.drive(new TextStyleTween(
|
|
begin: bottomTitleTextStyle,
|
|
end: topBackButtonTextStyle
|
|
)),
|
|
child: topBackLabel.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Widget topMiddle {
|
|
get {
|
|
KeyedSubtree topMiddle = (KeyedSubtree) topComponents.middleKey.currentWidget;
|
|
|
|
if (topMiddle == null) {
|
|
return null;
|
|
}
|
|
|
|
if (topHasUserMiddle != true && topLargeExpanded == true) {
|
|
return null;
|
|
}
|
|
|
|
RelativeRect to = positionInTransitionBox(topComponents.middleKey, from: topNavBarBox);
|
|
|
|
RelativeRectTween positionTween = new RelativeRectTween(
|
|
begin: to.shift(
|
|
new Offset(forwardDirection * topNavBarBox.size.width / 2.0f,
|
|
0.0f
|
|
)
|
|
),
|
|
end: to
|
|
);
|
|
|
|
return new PositionedTransition(
|
|
rect: animation.drive(positionTween),
|
|
child: new FadeTransition(
|
|
opacity: fadeInFrom(0.25f),
|
|
child: new DefaultTextStyle(
|
|
style: topTitleTextStyle,
|
|
child: topMiddle.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget topTrailing {
|
|
get {
|
|
KeyedSubtree topTrailing = (KeyedSubtree) topComponents.trailingKey.currentWidget;
|
|
|
|
if (topTrailing == null) {
|
|
return null;
|
|
}
|
|
|
|
return Positioned.fromRelativeRect(
|
|
rect: positionInTransitionBox(topComponents.trailingKey, from: topNavBarBox),
|
|
child: new FadeTransition(
|
|
opacity: fadeInFrom(0.4f),
|
|
child: topTrailing.child
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
public Widget topLargeTitle {
|
|
get {
|
|
KeyedSubtree topLargeTitle = (KeyedSubtree) topComponents.largeTitleKey.currentWidget;
|
|
|
|
if (topLargeTitle == null || topLargeExpanded != true) {
|
|
return null;
|
|
}
|
|
|
|
RelativeRect to =
|
|
positionInTransitionBox(topComponents.largeTitleKey, from: topNavBarBox);
|
|
|
|
RelativeRectTween positionTween = new RelativeRectTween(
|
|
begin: to.shift(
|
|
new Offset(forwardDirection * topNavBarBox.size.width,
|
|
0.0f
|
|
)
|
|
),
|
|
end: to
|
|
);
|
|
|
|
return new PositionedTransition(
|
|
rect: animation.drive(positionTween),
|
|
child: new FadeTransition(
|
|
opacity: fadeInFrom(0.3f),
|
|
child: new DefaultTextStyle(
|
|
style: topLargeTitleTextStyle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
child: topLargeTitle.child
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|