feat(ui): 增加视觉 token 与统一导航

This commit is contained in:
Zhou Weidong committed 2026-10-19 10:16:12 +08:00
1 parent 42ce1d1d84
commit df5fbe5b08
7 files changed
+260 -1

No files matched your search

+109
View File
@@ -0,0 +1,109 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
import '../theme/app_dimens.dart';
/// One destination rendered by [AppBottomNav].
class AppBottomNavItem {
const AppBottomNavItem({
required this.tabKey,
required this.icon,
required this.selectedIcon,
required this.label,
required this.onTap,
this.selected = false,
});
final Key tabKey;
final IconData icon;
final IconData selectedIcon;
final String label;
final VoidCallback onTap;
final bool selected;
}
/// Shared bottom navigation used by all root tabs and the player fallback.
class AppBottomNav extends StatelessWidget {
const AppBottomNav({
super.key,
required this.items,
this.backgroundColor = AppColors.bar,
});
final List<AppBottomNavItem> items;
final Color backgroundColor;
@override
Widget build(BuildContext context) => SafeArea(
top: false,
child: Container(
height: AppDimens.navHeight,
decoration: BoxDecoration(
color: backgroundColor,
border: const Border(
top: BorderSide(color: AppColors.outlineVariantDark),
),
),
child: Row(
children: [for (final item in items) _NavDestination(item: item)],
),
),
);
}
class _NavDestination extends StatelessWidget {
const _NavDestination({required this.item});
final AppBottomNavItem item;
@override
Widget build(BuildContext context) => Expanded(
child: Semantics(
button: true,
selected: item.selected,
label: item.label,
child: InkWell(
key: item.tabKey,
onTap: item.onTap,
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: AppDimens.tapTarget),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
padding: const EdgeInsets.symmetric(
horizontal: 14,
vertical: 3,
),
decoration: BoxDecoration(
color: item.selected ? AppColors.blue : Colors.transparent,
borderRadius: BorderRadius.circular(14),
),
child: Icon(
item.selected ? item.selectedIcon : item.icon,
size: AppDimens.navIcon,
color: item.selected ? AppColors.onPrimary : AppColors.muted,
),
),
const SizedBox(height: 2),
Text(
item.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: AppDimens.navLabel,
fontWeight: item.selected ? FontWeight.w700 : FontWeight.w500,
color: item.selected ? AppColors.blueSoft : AppColors.muted,
),
),
],
),
),
),
),
);
}