fix(flutter): 回退底部导航为四槽
This commit is contained in:
1 parent
e448003f1b
commit
2f1a4c5c69
10 files changed
+21
-139
No files matched your search
@@ -42,11 +42,6 @@ abstract final class AppColors {
|
|||||||
/// 底部导航底色。
|
/// 底部导航底色。
|
||||||
static const bar = Color(0xFFFFFDFB);
|
static const bar = Color(0xFFFFFDFB);
|
||||||
|
|
||||||
/// 中央操作按钮渐变。
|
|
||||||
static const navCenterStart = Color(0xFFFF9A3D);
|
|
||||||
static const navCenterEnd = Color(0xFFFF6A00);
|
|
||||||
static const navCenterShadow = Color(0x33FF7A1A);
|
|
||||||
|
|
||||||
/// 卡片轻阴影。
|
/// 卡片轻阴影。
|
||||||
static const cardShadow = Color(0x14FF8A3D);
|
static const cardShadow = Color(0x14FF8A3D);
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ abstract final class AppDimens {
|
|||||||
|
|
||||||
static const double navHeight = 64;
|
static const double navHeight = 64;
|
||||||
static const double navIcon = 20;
|
static const double navIcon = 20;
|
||||||
static const double navCenter = 56;
|
|
||||||
static const double navCenterIcon = 28;
|
|
||||||
static const double progressHeight = 3;
|
static const double progressHeight = 3;
|
||||||
static const double progressRadius = 2;
|
static const double progressRadius = 2;
|
||||||
static const double progressLineHeight = 3;
|
static const double progressLineHeight = 3;
|
||||||
|
|||||||
@@ -26,37 +26,21 @@ class AppBottomNavItem {
|
|||||||
final bool selected;
|
final bool selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 底部导航中央操作按钮。
|
/// 四槽底部导航,仅做浅色暖橙视觉换肤。
|
||||||
class AppBottomNavCenterAction {
|
|
||||||
const AppBottomNavCenterAction({
|
|
||||||
required this.label,
|
|
||||||
required this.onTap,
|
|
||||||
this.key = const Key('nav-center-action'),
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key key;
|
|
||||||
final String label;
|
|
||||||
final VoidCallback onTap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 全局五槽底部导航。
|
|
||||||
class AppBottomNav extends StatelessWidget {
|
class AppBottomNav extends StatelessWidget {
|
||||||
const AppBottomNav({
|
const AppBottomNav({
|
||||||
super.key,
|
super.key,
|
||||||
required this.items,
|
required this.items,
|
||||||
this.centerAction,
|
|
||||||
this.backgroundColor = AppColors.bar,
|
this.backgroundColor = AppColors.bar,
|
||||||
this.barKey,
|
this.barKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<AppBottomNavItem> items;
|
final List<AppBottomNavItem> items;
|
||||||
final AppBottomNavCenterAction? centerAction;
|
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
final Key? barKey;
|
final Key? barKey;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final action = centerAction;
|
|
||||||
return ClipRect(
|
return ClipRect(
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
||||||
@@ -69,21 +53,9 @@ class AppBottomNav extends StatelessWidget {
|
|||||||
color: backgroundColor.withValues(alpha: 0.95),
|
color: backgroundColor.withValues(alpha: 0.95),
|
||||||
border: const Border(top: BorderSide(color: AppColors.border)),
|
border: const Border(top: BorderSide(color: AppColors.border)),
|
||||||
),
|
),
|
||||||
child: action == null
|
child: Row(
|
||||||
? Row(
|
children: [for (final item in items) _NavDestination(item: item)],
|
||||||
children: [
|
),
|
||||||
for (final item in items) _NavDestination(item: item),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Row(
|
|
||||||
children: [
|
|
||||||
for (final item in items.take(2))
|
|
||||||
_NavDestination(item: item),
|
|
||||||
Expanded(child: _CenterDestination(action: action)),
|
|
||||||
for (final item in items.skip(2))
|
|
||||||
_NavDestination(item: item),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -143,53 +115,3 @@ class _NavDestination extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CenterDestination extends StatelessWidget {
|
|
||||||
const _CenterDestination({required this.action});
|
|
||||||
|
|
||||||
final AppBottomNavCenterAction action;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Center(
|
|
||||||
child: Semantics(
|
|
||||||
button: true,
|
|
||||||
label: action.label,
|
|
||||||
child: Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
clipBehavior: Clip.antiAlias,
|
|
||||||
child: Ink(
|
|
||||||
width: AppDimens.navCenter,
|
|
||||||
height: AppDimens.navCenter,
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [AppColors.navCenterStart, AppColors.navCenterEnd],
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppColors.navCenterShadow,
|
|
||||||
blurRadius: 14,
|
|
||||||
offset: Offset(0, 5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
key: action.key,
|
|
||||||
customBorder: const CircleBorder(),
|
|
||||||
onTap: action.onTap,
|
|
||||||
child: const Icon(
|
|
||||||
Icons.add_rounded,
|
|
||||||
color: AppColors.onPrimary,
|
|
||||||
size: AppDimens.navCenterIcon,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -340,10 +340,6 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
|
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
|
||||||
centerAction: AppBottomNavCenterAction(
|
|
||||||
label: l10n.createTab,
|
|
||||||
onTap: () => _act(DiscoverPageAction.create),
|
|
||||||
),
|
|
||||||
items: [
|
items: [
|
||||||
AppBottomNavItem(
|
AppBottomNavItem(
|
||||||
tabKey: const Key('discover-nav-home'),
|
tabKey: const Key('discover-nav-home'),
|
||||||
|
|||||||
@@ -195,10 +195,6 @@ class _HomeTabsState extends State<_HomeTabs> {
|
|||||||
Widget _nav(BuildContext context, AppLocalizations l10n) {
|
Widget _nav(BuildContext context, AppLocalizations l10n) {
|
||||||
return AppBottomNav(
|
return AppBottomNav(
|
||||||
barKey: const Key('home-tabs-bar'),
|
barKey: const Key('home-tabs-bar'),
|
||||||
centerAction: AppBottomNavCenterAction(
|
|
||||||
label: l10n.createTab,
|
|
||||||
onTap: () => _selectTab(context, 2),
|
|
||||||
),
|
|
||||||
items: [
|
items: [
|
||||||
_navItem(context, l10n, 0),
|
_navItem(context, l10n, 0),
|
||||||
_navItem(context, l10n, 1),
|
_navItem(context, l10n, 1),
|
||||||
|
|||||||
@@ -242,10 +242,6 @@ class _MyPageState extends State<MyPage> {
|
|||||||
|
|
||||||
Widget _bottomNav(AppLocalizations l10n) {
|
Widget _bottomNav(AppLocalizations l10n) {
|
||||||
return AppBottomNav(
|
return AppBottomNav(
|
||||||
centerAction: AppBottomNavCenterAction(
|
|
||||||
label: l10n.createTab,
|
|
||||||
onTap: () => _act(MyPageAction.create),
|
|
||||||
),
|
|
||||||
items: [
|
items: [
|
||||||
AppBottomNavItem(
|
AppBottomNavItem(
|
||||||
tabKey: const Key('mine-nav-home'),
|
tabKey: const Key('mine-nav-home'),
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import 'package:fruitshorts/core/theme/app_dimens.dart';
|
|||||||
import 'package:fruitshorts/core/widgets/app_bottom_nav.dart';
|
import 'package:fruitshorts/core/widgets/app_bottom_nav.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('should_render_five_slots_with_center_action', (tester) async {
|
testWidgets('should_render_four_slots_with_orange_selected_state', (
|
||||||
var centerTaps = 0;
|
tester,
|
||||||
|
) async {
|
||||||
|
var theaterTaps = 0;
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
@@ -27,7 +29,7 @@ void main() {
|
|||||||
icon: Icons.explore_outlined,
|
icon: Icons.explore_outlined,
|
||||||
selectedIcon: Icons.explore_rounded,
|
selectedIcon: Icons.explore_rounded,
|
||||||
label: 'Theater',
|
label: 'Theater',
|
||||||
onTap: () {},
|
onTap: () => theaterTaps++,
|
||||||
),
|
),
|
||||||
AppBottomNavItem(
|
AppBottomNavItem(
|
||||||
tabKey: const Key('tab-create'),
|
tabKey: const Key('tab-create'),
|
||||||
@@ -44,10 +46,6 @@ void main() {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
centerAction: AppBottomNavCenterAction(
|
|
||||||
label: 'Create',
|
|
||||||
onTap: () => centerTaps++,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -55,14 +53,13 @@ void main() {
|
|||||||
|
|
||||||
expect(find.byKey(const Key('tab-home')), findsOneWidget);
|
expect(find.byKey(const Key('tab-home')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('tab-theater')), findsOneWidget);
|
expect(find.byKey(const Key('tab-theater')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('nav-center-action')), findsOneWidget);
|
|
||||||
expect(find.byKey(const Key('tab-create')), findsOneWidget);
|
expect(find.byKey(const Key('tab-create')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
|
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('nav-center-action')), findsNothing);
|
||||||
expect(find.text('Home'), findsOneWidget);
|
expect(find.text('Home'), findsOneWidget);
|
||||||
final centers = [
|
final centers = [
|
||||||
Key('tab-home'),
|
Key('tab-home'),
|
||||||
Key('tab-theater'),
|
Key('tab-theater'),
|
||||||
Key('nav-center-action'),
|
|
||||||
Key('tab-create'),
|
Key('tab-create'),
|
||||||
Key('tab-mine'),
|
Key('tab-mine'),
|
||||||
].map((key) => tester.getCenter(find.byKey(key)).dx).toList();
|
].map((key) => tester.getCenter(find.byKey(key)).dx).toList();
|
||||||
@@ -72,25 +69,17 @@ void main() {
|
|||||||
AppDimens.navHeight,
|
AppDimens.navHeight,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
tester.getSize(find.byKey(const Key('nav-center-action'))),
|
tester
|
||||||
const Size.square(AppDimens.navCenter),
|
.widget<Icon>(
|
||||||
);
|
find.descendant(
|
||||||
final ink = tester.widget<Ink>(
|
of: find.byKey(const Key('tab-home')),
|
||||||
find
|
matching: find.byIcon(Icons.movie_rounded),
|
||||||
.ancestor(
|
),
|
||||||
of: find.byKey(const Key('nav-center-action')),
|
|
||||||
matching: find.byType(Ink),
|
|
||||||
)
|
)
|
||||||
.first,
|
.color,
|
||||||
|
AppColors.blue,
|
||||||
);
|
);
|
||||||
final decoration = ink.decoration! as BoxDecoration;
|
await tester.tap(find.byKey(const Key('tab-theater')));
|
||||||
expect(decoration.shape, BoxShape.circle);
|
expect(theaterTaps, 1);
|
||||||
expect((decoration.gradient! as LinearGradient).colors, const [
|
|
||||||
AppColors.navCenterStart,
|
|
||||||
AppColors.navCenterEnd,
|
|
||||||
]);
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('nav-center-action')));
|
|
||||||
expect(centerTaps, 1);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,6 @@ const _keys = [
|
|||||||
Key('home-tabs-bar'),
|
Key('home-tabs-bar'),
|
||||||
Key('tab-home'),
|
Key('tab-home'),
|
||||||
Key('tab-theater'),
|
Key('tab-theater'),
|
||||||
Key('nav-center-action'),
|
|
||||||
Key('tab-create'),
|
Key('tab-create'),
|
||||||
Key('tab-mine'),
|
Key('tab-mine'),
|
||||||
];
|
];
|
||||||
@@ -139,7 +138,6 @@ void main() {
|
|||||||
|
|
||||||
expect(find.byKey(const Key('tab-home')), findsOneWidget);
|
expect(find.byKey(const Key('tab-home')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('tab-theater')), findsOneWidget);
|
expect(find.byKey(const Key('tab-theater')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('nav-center-action')), findsOneWidget);
|
|
||||||
expect(find.byKey(const Key('tab-create')), findsOneWidget);
|
expect(find.byKey(const Key('tab-create')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
|
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('home-browse-page')), findsOneWidget);
|
expect(find.byKey(const Key('home-browse-page')), findsOneWidget);
|
||||||
@@ -148,12 +146,6 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget);
|
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('nav-center-action')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('tab-home')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
await tester.tap(find.byKey(const Key('tab-create')));
|
await tester.tap(find.byKey(const Key('tab-create')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
||||||
|
|||||||
@@ -33,9 +33,8 @@ void main() {
|
|||||||
for (final key in _tabKeys) {
|
for (final key in _tabKeys) {
|
||||||
expect(find.byKey(key), findsOneWidget);
|
expect(find.byKey(key), findsOneWidget);
|
||||||
}
|
}
|
||||||
expect(find.byKey(const Key('nav-center-action')), findsOneWidget);
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('nav-center-action')));
|
await tester.tap(find.byKey(const Key('tab-create')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ void main() {
|
|||||||
expect(AppDimens.primaryButtonHeight, 52);
|
expect(AppDimens.primaryButtonHeight, 52);
|
||||||
expect(AppDimens.tapTarget, 44);
|
expect(AppDimens.tapTarget, 44);
|
||||||
expect(AppDimens.navHeight, 64);
|
expect(AppDimens.navHeight, 64);
|
||||||
expect(AppDimens.navCenter, 56);
|
|
||||||
expect(AppDimens.progressHeight, 3);
|
expect(AppDimens.progressHeight, 3);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user