feat(ui): 增加视觉 token 与统一导航
This commit is contained in:
1 parent
42ce1d1d84
commit
df5fbe5b08
7 files changed
+260
-1
No files matched your search
@@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user