merge: 使用胶片盘图片导航图标

This commit is contained in:
Zhou Weidong committed 2026-10-22 19:24:40 +08:00
commit b1963a718e
6 files changed
+132 -47

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

+6
View File
@@ -1150,3 +1150,9 @@ $ flutter test
- `AppRootTab.home` 未选中改为 `theaters_outlined`,选中改为 `theaters`。
- 其余三个根导航图标、26 尺寸及白/灰颜色规则保持不变;测试断言同步。
- 按当前流程未执行 analyze/test。
## 2026-10-22 首页圆形胶片盘图标
- `AppRootTab` 新增可选 `assetPath`,首页使用 `assets/icon/film_reel.png`。
- 共享导航通过 `ColorFiltered + Image.asset` 按选中态着色,尺寸保持 26,缺图回退胶片 Material 图标。
- 推荐、追剧、我的图标不变;素材与测试断言同步,按当前流程未执行 analyze/test。
+59 -40
View File
@@ -14,11 +14,13 @@ class AppBottomNavItem {
required this.label,
required this.onTap,
this.selected = false,
this.assetPath,
});
final Key tabKey;
final IconData icon;
final IconData selectedIcon;
final String? assetPath;
final String label;
final VoidCallback onTap;
final bool selected;
@@ -61,49 +63,66 @@ class _NavDestination extends StatelessWidget {
final Color unselectedColor;
@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.navHeight),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedScale(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
scale: item.selected ? 1 : 0.96,
child: Icon(
item.selected ? item.selectedIcon : item.icon,
size: AppDimens.navIcon,
color: item.selected
? AppColors.navSelected
: unselectedColor,
Widget build(BuildContext context) {
final color = item.selected ? AppColors.navSelected : unselectedColor;
final fallbackIcon = item.selected ? item.selectedIcon : item.icon;
return 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.navHeight),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedScale(
duration: const Duration(milliseconds: 160),
curve: Curves.easeOut,
scale: item.selected ? 1 : 0.96,
child: item.assetPath == null
? Icon(
fallbackIcon,
size: AppDimens.navIcon,
color: color,
)
: ColorFiltered(
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
child: Image.asset(
item.assetPath!,
width: AppDimens.navIcon,
height: AppDimens.navIcon,
fit: BoxFit.contain,
errorBuilder: (_, _, _) => Icon(
item.icon,
size: AppDimens.navIcon,
color: color,
),
),
),
),
),
const SizedBox(height: AppDimens.navLabelGap),
Text(
item.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: AppDimens.navLabel,
fontWeight: item.selected ? FontWeight.w600 : FontWeight.w400,
color: item.selected
? AppColors.navSelected
: unselectedColor,
letterSpacing: 0,
const SizedBox(height: AppDimens.navLabelGap),
Text(
item.label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: AppDimens.navLabel,
fontWeight: item.selected
? FontWeight.w600
: FontWeight.w400,
color: color,
letterSpacing: 0,
),
),
),
],
],
),
),
),
),
),
);
);
}
}
+11 -1
View File
@@ -6,7 +6,16 @@ import '../../l10n/app_localizations.dart';
import 'app_bottom_nav.dart';
/// 客户端根导航的固定顺序。
enum AppRootTab { home, recommend, following, mine }
enum AppRootTab {
home(assetPath: 'assets/icon/film_reel.png'),
recommend(),
following(),
mine();
const AppRootTab({this.assetPath});
final String? assetPath;
}
extension AppRootTabUi on AppRootTab {
Key get tabKey => switch (this) {
@@ -62,6 +71,7 @@ class AppRootBottomNav extends StatelessWidget {
tabKey: tab.tabKey,
icon: tab.icon,
selectedIcon: tab.selectedIcon,
assetPath: tab.assetPath,
label: tab.label(l10n),
selected: tab == selected,
onTap: () => onSelected(tab),
+25
View File
@@ -179,4 +179,29 @@ void main() {
findsWidgets,
);
});
testWidgets('should_fallback_when_asset_icon_is_missing', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
bottomNavigationBar: AppBottomNav(
items: [
AppBottomNavItem(
tabKey: const Key('tab-home'),
icon: Icons.theaters_outlined,
selectedIcon: Icons.theaters,
assetPath: 'assets/icon/missing_film_reel.png',
label: 'Home',
selected: true,
onTap: () {},
),
],
),
),
),
);
await tester.pumpAndSettle();
expect(find.byIcon(Icons.theaters_outlined), findsOneWidget);
});
}
+31 -6
View File
@@ -191,7 +191,7 @@ void main() {
expect(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byIcon(Icons.theaters),
matching: find.byType(Image),
),
findsOneWidget,
);
@@ -218,14 +218,28 @@ void main() {
);
expect(
tester
.widget<Icon>(
.widget<ColorFiltered>(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byIcon(Icons.theaters),
matching: find.byType(ColorFiltered),
),
)
.color,
AppColors.navSelected,
.colorFilter,
const ColorFilter.mode(AppColors.navSelected, BlendMode.srcIn),
);
final selectedHomeImage = tester.widget<Image>(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byType(Image),
),
);
expect(
(selectedHomeImage.image as AssetImage).assetName,
'assets/icon/film_reel.png',
);
expect(
Size(selectedHomeImage.width!, selectedHomeImage.height!),
const Size.square(AppDimens.navIcon),
);
await tester.tap(find.byKey(const Key('tab-recommend')));
@@ -271,10 +285,21 @@ void main() {
expect(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byIcon(Icons.theaters_outlined),
matching: find.byType(Image),
),
findsOneWidget,
);
expect(
tester
.widget<ColorFiltered>(
find.descendant(
of: find.byKey(const Key('tab-home')),
matching: find.byType(ColorFiltered),
),
)
.colorFilter,
const ColorFilter.mode(AppColors.navUnselected, BlendMode.srcIn),
);
});
testWidgets('should_map_four_tabs_to_home_recommend_following_and_me', (