mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
778 lines
23 KiB
Dart
778 lines
23 KiB
Dart
// Copyright 2014 The Flutter 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:PiliPlus/utils/extension/theme_ext.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
const double _kMaxLabelTextScaleFactor = 1.3;
|
|
|
|
const _kNavigationHeight = 64.0;
|
|
const _kIndicatorHeight = _kNavigationHeight - 2 * _kIndicatorPaddingInt;
|
|
const _kIndicatorWidth = 86.0;
|
|
const _kIndicatorPaddingInt = 4.0;
|
|
const _kIndicatorPadding = EdgeInsets.all(_kIndicatorPaddingInt);
|
|
const _kBorderRadius = BorderRadius.all(.circular(_kNavigationHeight / 2));
|
|
const _kNavigationShape = RoundedSuperellipseBorder(
|
|
borderRadius: _kBorderRadius,
|
|
);
|
|
|
|
/// ref [NavigationBar]
|
|
class FloatingNavigationBar extends StatelessWidget {
|
|
// ignore: prefer_const_constructors_in_immutables
|
|
FloatingNavigationBar({
|
|
super.key,
|
|
this.animationDuration = const Duration(milliseconds: 500),
|
|
this.selectedIndex = 0,
|
|
required this.destinations,
|
|
this.onDestinationSelected,
|
|
this.backgroundColor,
|
|
this.elevation,
|
|
this.shadowColor,
|
|
this.surfaceTintColor,
|
|
this.indicatorColor,
|
|
this.indicatorShape,
|
|
this.labelBehavior,
|
|
this.overlayColor,
|
|
this.labelTextStyle,
|
|
this.labelPadding,
|
|
this.bottomPadding = 8.0,
|
|
}) : assert(destinations.length >= 2),
|
|
assert(0 <= selectedIndex && selectedIndex < destinations.length);
|
|
|
|
final Duration animationDuration;
|
|
final int selectedIndex;
|
|
final List<Widget> destinations;
|
|
final ValueChanged<int>? onDestinationSelected;
|
|
final Color? backgroundColor;
|
|
final double? elevation;
|
|
final Color? shadowColor;
|
|
final Color? surfaceTintColor;
|
|
final Color? indicatorColor;
|
|
final ShapeBorder? indicatorShape;
|
|
final NavigationDestinationLabelBehavior? labelBehavior;
|
|
final WidgetStateProperty<Color?>? overlayColor;
|
|
final WidgetStateProperty<TextStyle?>? labelTextStyle;
|
|
final EdgeInsetsGeometry? labelPadding;
|
|
final double bottomPadding;
|
|
|
|
VoidCallback _handleTap(int index) {
|
|
return onDestinationSelected != null
|
|
? () => onDestinationSelected!(index)
|
|
: () {};
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final defaults = _NavigationBarDefaultsM3(context);
|
|
|
|
final navigationBarTheme = NavigationBarTheme.of(context);
|
|
final effectiveLabelBehavior =
|
|
labelBehavior ??
|
|
navigationBarTheme.labelBehavior ??
|
|
defaults.labelBehavior!;
|
|
|
|
final padding = MediaQuery.viewPaddingOf(context);
|
|
|
|
return UnconstrainedBox(
|
|
child: Padding(
|
|
padding: .fromLTRB(
|
|
padding.left,
|
|
0,
|
|
padding.right,
|
|
bottomPadding + padding.bottom,
|
|
),
|
|
child: SizedBox(
|
|
height: _kNavigationHeight,
|
|
width: destinations.length * _kIndicatorWidth,
|
|
child: DecoratedBox(
|
|
decoration: ShapeDecoration(
|
|
color: ElevationOverlay.applySurfaceTint(
|
|
backgroundColor ??
|
|
navigationBarTheme.backgroundColor ??
|
|
defaults.backgroundColor!,
|
|
surfaceTintColor ??
|
|
navigationBarTheme.surfaceTintColor ??
|
|
defaults.surfaceTintColor,
|
|
elevation ??
|
|
navigationBarTheme.elevation ??
|
|
defaults.elevation!,
|
|
),
|
|
shape: RoundedSuperellipseBorder(
|
|
side: defaults.borderSide,
|
|
borderRadius: _kBorderRadius,
|
|
),
|
|
),
|
|
child: Padding(
|
|
padding: _kIndicatorPadding,
|
|
child: Row(
|
|
crossAxisAlignment: .stretch,
|
|
children: <Widget>[
|
|
for (int i = 0; i < destinations.length; i++)
|
|
Expanded(
|
|
child: _SelectableAnimatedBuilder(
|
|
duration: animationDuration,
|
|
isSelected: i == selectedIndex,
|
|
builder: (context, animation) {
|
|
return _NavigationDestinationInfo(
|
|
index: i,
|
|
selectedIndex: selectedIndex,
|
|
totalNumberOfDestinations: destinations.length,
|
|
selectedAnimation: animation,
|
|
labelBehavior: effectiveLabelBehavior,
|
|
indicatorColor: indicatorColor,
|
|
indicatorShape: indicatorShape,
|
|
overlayColor: overlayColor,
|
|
onTap: _handleTap(i),
|
|
labelTextStyle: labelTextStyle,
|
|
labelPadding: labelPadding,
|
|
child: destinations[i],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class FloatingNavigationDestination extends StatelessWidget {
|
|
const FloatingNavigationDestination({
|
|
super.key,
|
|
required this.icon,
|
|
this.selectedIcon,
|
|
required this.label,
|
|
this.tooltip,
|
|
this.enabled = true,
|
|
});
|
|
|
|
final Widget icon;
|
|
|
|
final Widget? selectedIcon;
|
|
|
|
final String label;
|
|
|
|
final String? tooltip;
|
|
|
|
final bool enabled;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final info = _NavigationDestinationInfo.of(context);
|
|
const selectedState = <WidgetState>{WidgetState.selected};
|
|
const unselectedState = <WidgetState>{};
|
|
const disabledState = <WidgetState>{WidgetState.disabled};
|
|
|
|
final navigationBarTheme = NavigationBarTheme.of(context);
|
|
final defaults = _NavigationBarDefaultsM3(context);
|
|
final animation = info.selectedAnimation;
|
|
|
|
return Stack(
|
|
alignment: .center,
|
|
clipBehavior: .none,
|
|
children: [
|
|
NavigationIndicator(
|
|
animation: animation,
|
|
color:
|
|
info.indicatorColor ??
|
|
navigationBarTheme.indicatorColor ??
|
|
defaults.indicatorColor!,
|
|
),
|
|
_NavigationDestinationBuilder(
|
|
label: label,
|
|
tooltip: tooltip,
|
|
enabled: enabled,
|
|
buildIcon: (context) {
|
|
final IconThemeData selectedIconTheme =
|
|
navigationBarTheme.iconTheme?.resolve(selectedState) ??
|
|
defaults.iconTheme!.resolve(selectedState)!;
|
|
final IconThemeData unselectedIconTheme =
|
|
navigationBarTheme.iconTheme?.resolve(unselectedState) ??
|
|
defaults.iconTheme!.resolve(unselectedState)!;
|
|
final IconThemeData disabledIconTheme =
|
|
navigationBarTheme.iconTheme?.resolve(disabledState) ??
|
|
defaults.iconTheme!.resolve(disabledState)!;
|
|
|
|
final Widget selectedIconWidget = IconTheme.merge(
|
|
data: enabled ? selectedIconTheme : disabledIconTheme,
|
|
child: selectedIcon ?? icon,
|
|
);
|
|
final Widget unselectedIconWidget = IconTheme.merge(
|
|
data: enabled ? unselectedIconTheme : disabledIconTheme,
|
|
child: icon,
|
|
);
|
|
return _StatusTransitionWidgetBuilder(
|
|
animation: animation,
|
|
builder: (context, child) {
|
|
return animation.isForwardOrCompleted
|
|
? selectedIconWidget
|
|
: unselectedIconWidget;
|
|
},
|
|
);
|
|
},
|
|
buildLabel: (context) {
|
|
final TextStyle? effectiveSelectedLabelTextStyle =
|
|
info.labelTextStyle?.resolve(selectedState) ??
|
|
navigationBarTheme.labelTextStyle?.resolve(selectedState) ??
|
|
defaults.labelTextStyle!.resolve(selectedState);
|
|
final TextStyle? effectiveUnselectedLabelTextStyle =
|
|
info.labelTextStyle?.resolve(unselectedState) ??
|
|
navigationBarTheme.labelTextStyle?.resolve(unselectedState) ??
|
|
defaults.labelTextStyle!.resolve(unselectedState);
|
|
final TextStyle? effectiveDisabledLabelTextStyle =
|
|
info.labelTextStyle?.resolve(disabledState) ??
|
|
navigationBarTheme.labelTextStyle?.resolve(disabledState) ??
|
|
defaults.labelTextStyle!.resolve(disabledState);
|
|
final EdgeInsetsGeometry labelPadding =
|
|
info.labelPadding ??
|
|
navigationBarTheme.labelPadding ??
|
|
defaults.labelPadding!;
|
|
|
|
final textStyle = enabled
|
|
? animation.isForwardOrCompleted
|
|
? effectiveSelectedLabelTextStyle
|
|
: effectiveUnselectedLabelTextStyle
|
|
: effectiveDisabledLabelTextStyle;
|
|
|
|
return Padding(
|
|
padding: labelPadding,
|
|
child: MediaQuery.withClampedTextScaling(
|
|
maxScaleFactor: _kMaxLabelTextScaleFactor,
|
|
child: Text(label, style: textStyle),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationDestinationBuilder extends StatefulWidget {
|
|
const _NavigationDestinationBuilder({
|
|
required this.buildIcon,
|
|
required this.buildLabel,
|
|
required this.label,
|
|
this.tooltip,
|
|
this.enabled = true,
|
|
});
|
|
|
|
final WidgetBuilder buildIcon;
|
|
|
|
final WidgetBuilder buildLabel;
|
|
|
|
final String label;
|
|
|
|
final String? tooltip;
|
|
|
|
final bool enabled;
|
|
|
|
@override
|
|
State<_NavigationDestinationBuilder> createState() =>
|
|
_NavigationDestinationBuilderState();
|
|
}
|
|
|
|
class _NavigationDestinationBuilderState
|
|
extends State<_NavigationDestinationBuilder> {
|
|
final GlobalKey iconKey = GlobalKey();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final info = _NavigationDestinationInfo.of(context);
|
|
|
|
final child = GestureDetector(
|
|
behavior: .opaque,
|
|
onTap: widget.enabled ? info.onTap : null,
|
|
child: _NavigationBarDestinationLayout(
|
|
icon: widget.buildIcon(context),
|
|
iconKey: iconKey,
|
|
label: widget.buildLabel(context),
|
|
),
|
|
);
|
|
if (info.labelBehavior == .alwaysShow) {
|
|
return child;
|
|
}
|
|
return _NavigationBarDestinationTooltip(
|
|
message: widget.tooltip ?? widget.label,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationDestinationInfo extends InheritedWidget {
|
|
const _NavigationDestinationInfo({
|
|
required this.index,
|
|
required this.selectedIndex,
|
|
required this.totalNumberOfDestinations,
|
|
required this.selectedAnimation,
|
|
required this.labelBehavior,
|
|
required this.indicatorColor,
|
|
required this.indicatorShape,
|
|
required this.overlayColor,
|
|
required this.onTap,
|
|
this.labelTextStyle,
|
|
this.labelPadding,
|
|
required super.child,
|
|
});
|
|
|
|
final int index;
|
|
|
|
final int selectedIndex;
|
|
|
|
final int totalNumberOfDestinations;
|
|
|
|
final Animation<double> selectedAnimation;
|
|
|
|
final NavigationDestinationLabelBehavior labelBehavior;
|
|
|
|
final Color? indicatorColor;
|
|
|
|
final ShapeBorder? indicatorShape;
|
|
|
|
final WidgetStateProperty<Color?>? overlayColor;
|
|
|
|
final VoidCallback onTap;
|
|
|
|
final WidgetStateProperty<TextStyle?>? labelTextStyle;
|
|
|
|
final EdgeInsetsGeometry? labelPadding;
|
|
|
|
static _NavigationDestinationInfo of(BuildContext context) {
|
|
final _NavigationDestinationInfo? result = context
|
|
.dependOnInheritedWidgetOfExactType<_NavigationDestinationInfo>();
|
|
assert(
|
|
result != null,
|
|
'Navigation destinations need a _NavigationDestinationInfo parent, '
|
|
'which is usually provided by NavigationBar.',
|
|
);
|
|
return result!;
|
|
}
|
|
|
|
@override
|
|
bool updateShouldNotify(_NavigationDestinationInfo oldWidget) {
|
|
return index != oldWidget.index ||
|
|
totalNumberOfDestinations != oldWidget.totalNumberOfDestinations ||
|
|
selectedAnimation != oldWidget.selectedAnimation ||
|
|
labelBehavior != oldWidget.labelBehavior ||
|
|
onTap != oldWidget.onTap;
|
|
}
|
|
}
|
|
|
|
class NavigationIndicator extends StatelessWidget {
|
|
const NavigationIndicator({
|
|
super.key,
|
|
required this.animation,
|
|
this.color,
|
|
this.width = _kIndicatorWidth,
|
|
this.height = _kIndicatorHeight,
|
|
});
|
|
|
|
final Animation<double> animation;
|
|
|
|
final Color? color;
|
|
|
|
final double width;
|
|
|
|
final double height;
|
|
|
|
static final _anim = Tween<double>(
|
|
begin: .5,
|
|
end: 1.0,
|
|
).chain(CurveTween(curve: Curves.easeInOutCubicEmphasized));
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: animation,
|
|
builder: (context, child) {
|
|
final double scale = animation.isDismissed
|
|
? 0.0
|
|
: _anim.evaluate(animation);
|
|
|
|
return Transform(
|
|
alignment: Alignment.center,
|
|
transform: Matrix4.diagonal3Values(scale, 1.0, 1.0),
|
|
child: child,
|
|
);
|
|
},
|
|
|
|
child: _StatusTransitionWidgetBuilder(
|
|
animation: animation,
|
|
builder: (context, child) {
|
|
return _SelectableAnimatedBuilder(
|
|
isSelected: animation.isForwardOrCompleted,
|
|
duration: const Duration(milliseconds: 100),
|
|
alwaysDoFullAnimation: true,
|
|
builder: (context, fadeAnimation) {
|
|
return FadeTransition(
|
|
opacity: fadeAnimation,
|
|
child: DecoratedBox(
|
|
decoration: ShapeDecoration(
|
|
shape: _kNavigationShape,
|
|
color: color ?? Theme.of(context).colorScheme.secondary,
|
|
),
|
|
child: const SizedBox(
|
|
width: _kIndicatorWidth,
|
|
height: _kIndicatorHeight,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationBarDestinationLayout extends StatelessWidget {
|
|
const _NavigationBarDestinationLayout({
|
|
required this.icon,
|
|
required this.iconKey,
|
|
required this.label,
|
|
});
|
|
|
|
final Widget icon;
|
|
|
|
final GlobalKey iconKey;
|
|
|
|
final Widget label;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return _DestinationLayoutAnimationBuilder(
|
|
builder: (context, animation) {
|
|
return CustomMultiChildLayout(
|
|
delegate: _NavigationDestinationLayoutDelegate(animation: animation),
|
|
children: <Widget>[
|
|
LayoutId(
|
|
id: _NavigationDestinationLayoutDelegate.iconId,
|
|
child: KeyedSubtree(key: iconKey, child: icon),
|
|
),
|
|
LayoutId(
|
|
id: _NavigationDestinationLayoutDelegate.labelId,
|
|
child: FadeTransition(
|
|
alwaysIncludeSemantics: true,
|
|
opacity: animation,
|
|
child: label,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DestinationLayoutAnimationBuilder extends StatelessWidget {
|
|
const _DestinationLayoutAnimationBuilder({required this.builder});
|
|
|
|
final Widget Function(BuildContext, Animation<double>) builder;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final info = _NavigationDestinationInfo.of(context);
|
|
switch (info.labelBehavior) {
|
|
case NavigationDestinationLabelBehavior.alwaysShow:
|
|
return builder(context, kAlwaysCompleteAnimation);
|
|
case NavigationDestinationLabelBehavior.alwaysHide:
|
|
return builder(context, kAlwaysDismissedAnimation);
|
|
case NavigationDestinationLabelBehavior.onlyShowSelected:
|
|
return _CurvedAnimationBuilder(
|
|
animation: info.selectedAnimation,
|
|
curve: Curves.easeInOutCubicEmphasized,
|
|
reverseCurve: Curves.easeInOutCubicEmphasized.flipped,
|
|
builder: builder,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _NavigationBarDestinationTooltip extends StatelessWidget {
|
|
const _NavigationBarDestinationTooltip({
|
|
required this.message,
|
|
required this.child,
|
|
});
|
|
|
|
final String message;
|
|
|
|
final Widget child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Tooltip(
|
|
message: message,
|
|
verticalOffset: 34,
|
|
excludeFromSemantics: true,
|
|
preferBelow: false,
|
|
child: child,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _NavigationDestinationLayoutDelegate extends MultiChildLayoutDelegate {
|
|
_NavigationDestinationLayoutDelegate({required this.animation})
|
|
: super(relayout: animation);
|
|
|
|
final Animation<double> animation;
|
|
|
|
static const int iconId = 1;
|
|
|
|
static const int labelId = 2;
|
|
|
|
@override
|
|
void performLayout(Size size) {
|
|
double halfWidth(Size size) => size.width / 2;
|
|
double halfHeight(Size size) => size.height / 2;
|
|
|
|
final Size iconSize = layoutChild(iconId, BoxConstraints.loose(size));
|
|
final Size labelSize = layoutChild(labelId, BoxConstraints.loose(size));
|
|
|
|
final double yPositionOffset = Tween<double>(
|
|
begin: halfHeight(iconSize),
|
|
|
|
end: halfHeight(iconSize) + halfHeight(labelSize),
|
|
).transform(animation.value);
|
|
final double iconYPosition = halfHeight(size) - yPositionOffset;
|
|
|
|
positionChild(
|
|
iconId,
|
|
Offset(
|
|
halfWidth(size) - halfWidth(iconSize),
|
|
iconYPosition,
|
|
),
|
|
);
|
|
|
|
positionChild(
|
|
labelId,
|
|
Offset(
|
|
halfWidth(size) - halfWidth(labelSize),
|
|
|
|
iconYPosition + iconSize.height,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRelayout(_NavigationDestinationLayoutDelegate oldDelegate) {
|
|
return oldDelegate.animation != animation;
|
|
}
|
|
}
|
|
|
|
class _StatusTransitionWidgetBuilder extends StatusTransitionWidget {
|
|
const _StatusTransitionWidgetBuilder({
|
|
required super.animation,
|
|
required this.builder,
|
|
// ignore: unused_element_parameter
|
|
this.child,
|
|
});
|
|
|
|
final TransitionBuilder builder;
|
|
|
|
final Widget? child;
|
|
|
|
@override
|
|
Widget build(BuildContext context) => builder(context, child);
|
|
}
|
|
|
|
class _SelectableAnimatedBuilder extends StatefulWidget {
|
|
const _SelectableAnimatedBuilder({
|
|
required this.isSelected,
|
|
this.duration = const Duration(milliseconds: 200),
|
|
this.alwaysDoFullAnimation = false,
|
|
required this.builder,
|
|
});
|
|
|
|
final bool isSelected;
|
|
|
|
final Duration duration;
|
|
|
|
final bool alwaysDoFullAnimation;
|
|
|
|
final Widget Function(BuildContext, Animation<double>) builder;
|
|
|
|
@override
|
|
_SelectableAnimatedBuilderState createState() =>
|
|
_SelectableAnimatedBuilderState();
|
|
}
|
|
|
|
class _SelectableAnimatedBuilderState extends State<_SelectableAnimatedBuilder>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(vsync: this);
|
|
_controller.duration = widget.duration;
|
|
_controller.value = widget.isSelected ? 1.0 : 0.0;
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(_SelectableAnimatedBuilder oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (oldWidget.duration != widget.duration) {
|
|
_controller.duration = widget.duration;
|
|
}
|
|
if (oldWidget.isSelected != widget.isSelected) {
|
|
if (widget.isSelected) {
|
|
_controller.forward(from: widget.alwaysDoFullAnimation ? 0 : null);
|
|
} else {
|
|
_controller.reverse(from: widget.alwaysDoFullAnimation ? 1 : null);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return widget.builder(context, _controller);
|
|
}
|
|
}
|
|
|
|
class _CurvedAnimationBuilder extends StatefulWidget {
|
|
const _CurvedAnimationBuilder({
|
|
required this.animation,
|
|
required this.curve,
|
|
required this.reverseCurve,
|
|
required this.builder,
|
|
});
|
|
|
|
final Animation<double> animation;
|
|
final Curve curve;
|
|
final Curve reverseCurve;
|
|
final Widget Function(BuildContext, Animation<double>) builder;
|
|
|
|
@override
|
|
_CurvedAnimationBuilderState createState() => _CurvedAnimationBuilderState();
|
|
}
|
|
|
|
class _CurvedAnimationBuilderState extends State<_CurvedAnimationBuilder> {
|
|
late AnimationStatus _animationDirection;
|
|
AnimationStatus? _preservedDirection;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_animationDirection = widget.animation.status;
|
|
_updateStatus(widget.animation.status);
|
|
widget.animation.addStatusListener(_updateStatus);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.animation.removeStatusListener(_updateStatus);
|
|
super.dispose();
|
|
}
|
|
|
|
void _updateStatus(AnimationStatus status) {
|
|
if (_animationDirection != status) {
|
|
setState(() {
|
|
_animationDirection = status;
|
|
});
|
|
}
|
|
switch (status) {
|
|
case AnimationStatus.forward || AnimationStatus.reverse
|
|
when _preservedDirection != null:
|
|
break;
|
|
case AnimationStatus.forward || AnimationStatus.reverse:
|
|
setState(() {
|
|
_preservedDirection = status;
|
|
});
|
|
case AnimationStatus.completed || AnimationStatus.dismissed:
|
|
setState(() {
|
|
_preservedDirection = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final shouldUseForwardCurve =
|
|
(_preservedDirection ?? _animationDirection) != AnimationStatus.reverse;
|
|
|
|
final Animation<double> curvedAnimation = CurveTween(
|
|
curve: shouldUseForwardCurve ? widget.curve : widget.reverseCurve,
|
|
).animate(widget.animation);
|
|
|
|
return widget.builder(context, curvedAnimation);
|
|
}
|
|
}
|
|
|
|
const _indicatorDark = Color(0x15FFFFFF);
|
|
const _indicatorLight = Color(0x10000000);
|
|
|
|
class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
|
|
_NavigationBarDefaultsM3(this.context)
|
|
: super(
|
|
height: _kNavigationHeight,
|
|
elevation: 3.0,
|
|
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
|
);
|
|
|
|
final BuildContext context;
|
|
late final _colors = Theme.of(context).colorScheme;
|
|
late final _textTheme = Theme.of(context).textTheme;
|
|
|
|
BorderSide get borderSide => _colors.brightness.isDark
|
|
? const BorderSide(color: Color(0x08FFFFFF))
|
|
: const BorderSide(color: Color(0x08000000));
|
|
|
|
@override
|
|
Color? get backgroundColor => _colors.surfaceContainer;
|
|
|
|
@override
|
|
Color? get shadowColor => Colors.transparent;
|
|
|
|
@override
|
|
Color? get surfaceTintColor => Colors.transparent;
|
|
|
|
@override
|
|
WidgetStateProperty<IconThemeData?>? get iconTheme {
|
|
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
return IconThemeData(
|
|
size: 24.0,
|
|
color: states.contains(WidgetState.disabled)
|
|
? _colors.onSurfaceVariant.withValues(alpha: 0.38)
|
|
: states.contains(WidgetState.selected)
|
|
? _colors.onSecondaryContainer
|
|
: _colors.onSurfaceVariant,
|
|
);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Color? get indicatorColor =>
|
|
_colors.brightness.isDark ? _indicatorDark : _indicatorLight;
|
|
|
|
@override
|
|
ShapeBorder? get indicatorShape => const StadiumBorder();
|
|
|
|
@override
|
|
WidgetStateProperty<TextStyle?>? get labelTextStyle {
|
|
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
final TextStyle style = _textTheme.labelMedium!;
|
|
return style.apply(
|
|
color: states.contains(WidgetState.disabled)
|
|
? _colors.onSurfaceVariant.withValues(alpha: 0.38)
|
|
: states.contains(WidgetState.selected)
|
|
? _colors.onSurface
|
|
: _colors.onSurfaceVariant,
|
|
);
|
|
});
|
|
}
|
|
|
|
@override
|
|
EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 2);
|
|
}
|