merge: 合并 K28-S 四槽导航修正
This commit is contained in:
commit
678b976ce1
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 navCenterStart = Color(0xFFFF9A3D);
|
||||
static const navCenterEnd = Color(0xFFFF6A00);
|
||||
static const navCenterShadow = Color(0x33FF7A1A);
|
||||
|
||||
/// 卡片轻阴影。
|
||||
static const cardShadow = Color(0x14FF8A3D);
|
||||
|
||||
|
||||
@@ -47,8 +47,6 @@ abstract final class AppDimens {
|
||||
|
||||
static const double navHeight = 64;
|
||||
static const double navIcon = 20;
|
||||
static const double navCenter = 56;
|
||||
static const double navCenterIcon = 28;
|
||||
static const double progressHeight = 3;
|
||||
static const double progressRadius = 2;
|
||||
static const double progressLineHeight = 3;
|
||||
|
||||
@@ -26,37 +26,21 @@ class AppBottomNavItem {
|
||||
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 {
|
||||
const AppBottomNav({
|
||||
super.key,
|
||||
required this.items,
|
||||
this.centerAction,
|
||||
this.backgroundColor = AppColors.bar,
|
||||
this.barKey,
|
||||
});
|
||||
|
||||
final List<AppBottomNavItem> items;
|
||||
final AppBottomNavCenterAction? centerAction;
|
||||
final Color backgroundColor;
|
||||
final Key? barKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final action = centerAction;
|
||||
return ClipRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 20, sigmaY: 20),
|
||||
@@ -69,21 +53,9 @@ class AppBottomNav extends StatelessWidget {
|
||||
color: backgroundColor.withValues(alpha: 0.95),
|
||||
border: const Border(top: BorderSide(color: AppColors.border)),
|
||||
),
|
||||
child: action == null
|
||||
? Row(
|
||||
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),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
children: [for (final item in items) _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(
|
||||
centerAction: AppBottomNavCenterAction(
|
||||
label: l10n.createTab,
|
||||
onTap: () => _act(DiscoverPageAction.create),
|
||||
),
|
||||
items: [
|
||||
AppBottomNavItem(
|
||||
tabKey: const Key('discover-nav-home'),
|
||||
|
||||
@@ -195,10 +195,6 @@ class _HomeTabsState extends State<_HomeTabs> {
|
||||
Widget _nav(BuildContext context, AppLocalizations l10n) {
|
||||
return AppBottomNav(
|
||||
barKey: const Key('home-tabs-bar'),
|
||||
centerAction: AppBottomNavCenterAction(
|
||||
label: l10n.createTab,
|
||||
onTap: () => _selectTab(context, 2),
|
||||
),
|
||||
items: [
|
||||
_navItem(context, l10n, 0),
|
||||
_navItem(context, l10n, 1),
|
||||
|
||||
@@ -242,10 +242,6 @@ class _MyPageState extends State<MyPage> {
|
||||
|
||||
Widget _bottomNav(AppLocalizations l10n) {
|
||||
return AppBottomNav(
|
||||
centerAction: AppBottomNavCenterAction(
|
||||
label: l10n.createTab,
|
||||
onTap: () => _act(MyPageAction.create),
|
||||
),
|
||||
items: [
|
||||
AppBottomNavItem(
|
||||
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';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_render_five_slots_with_center_action', (tester) async {
|
||||
var centerTaps = 0;
|
||||
testWidgets('should_render_four_slots_with_orange_selected_state', (
|
||||
tester,
|
||||
) async {
|
||||
var theaterTaps = 0;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
@@ -27,7 +29,7 @@ void main() {
|
||||
icon: Icons.explore_outlined,
|
||||
selectedIcon: Icons.explore_rounded,
|
||||
label: 'Theater',
|
||||
onTap: () {},
|
||||
onTap: () => theaterTaps++,
|
||||
),
|
||||
AppBottomNavItem(
|
||||
tabKey: const Key('tab-create'),
|
||||
@@ -44,10 +46,6 @@ void main() {
|
||||
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-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-mine')), findsOneWidget);
|
||||
expect(find.byKey(const Key('nav-center-action')), findsNothing);
|
||||
expect(find.text('Home'), findsOneWidget);
|
||||
final centers = [
|
||||
Key('tab-home'),
|
||||
Key('tab-theater'),
|
||||
Key('nav-center-action'),
|
||||
Key('tab-create'),
|
||||
Key('tab-mine'),
|
||||
].map((key) => tester.getCenter(find.byKey(key)).dx).toList();
|
||||
@@ -72,25 +69,17 @@ void main() {
|
||||
AppDimens.navHeight,
|
||||
);
|
||||
expect(
|
||||
tester.getSize(find.byKey(const Key('nav-center-action'))),
|
||||
const Size.square(AppDimens.navCenter),
|
||||
);
|
||||
final ink = tester.widget<Ink>(
|
||||
find
|
||||
.ancestor(
|
||||
of: find.byKey(const Key('nav-center-action')),
|
||||
matching: find.byType(Ink),
|
||||
tester
|
||||
.widget<Icon>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('tab-home')),
|
||||
matching: find.byIcon(Icons.movie_rounded),
|
||||
),
|
||||
)
|
||||
.first,
|
||||
.color,
|
||||
AppColors.blue,
|
||||
);
|
||||
final decoration = ink.decoration! as BoxDecoration;
|
||||
expect(decoration.shape, BoxShape.circle);
|
||||
expect((decoration.gradient! as LinearGradient).colors, const [
|
||||
AppColors.navCenterStart,
|
||||
AppColors.navCenterEnd,
|
||||
]);
|
||||
|
||||
await tester.tap(find.byKey(const Key('nav-center-action')));
|
||||
expect(centerTaps, 1);
|
||||
await tester.tap(find.byKey(const Key('tab-theater')));
|
||||
expect(theaterTaps, 1);
|
||||
});
|
||||
}
|
||||
@@ -20,7 +20,6 @@ const _keys = [
|
||||
Key('home-tabs-bar'),
|
||||
Key('tab-home'),
|
||||
Key('tab-theater'),
|
||||
Key('nav-center-action'),
|
||||
Key('tab-create'),
|
||||
Key('tab-mine'),
|
||||
];
|
||||
@@ -139,7 +138,6 @@ void main() {
|
||||
|
||||
expect(find.byKey(const Key('tab-home')), 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-mine')), findsOneWidget);
|
||||
expect(find.byKey(const Key('home-browse-page')), findsOneWidget);
|
||||
@@ -148,12 +146,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
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.pumpAndSettle();
|
||||
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
||||
|
||||
@@ -33,9 +33,8 @@ void main() {
|
||||
for (final key in _tabKeys) {
|
||||
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();
|
||||
|
||||
expect(find.byKey(const Key('create-page')), findsOneWidget);
|
||||
|
||||
@@ -21,7 +21,6 @@ void main() {
|
||||
expect(AppDimens.primaryButtonHeight, 52);
|
||||
expect(AppDimens.tapTarget, 44);
|
||||
expect(AppDimens.navHeight, 64);
|
||||
expect(AppDimens.navCenter, 56);
|
||||
expect(AppDimens.progressHeight, 3);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user