style(nav): 对齐四 Tab 底栏布局规格
This commit is contained in:
1 parent
667fdc5f0a
commit
81787dc3aa
10 files changed
+103
-106
No files matched your search
@@ -69,10 +69,9 @@ abstract final class AppColors {
|
||||
/// 底部导航底色。
|
||||
static const bar = ink;
|
||||
|
||||
/// 底部导航专用:纯黑背景、选中白、未选中灰与轻分割线。
|
||||
/// 底部导航专用:纯黑背景、选中白与未选中灰。
|
||||
static const navBackground = Color(0xFF000000);
|
||||
static const navSelected = Color(0xFFFFFFFF);
|
||||
static const navDivider = Color(0xFF1C1C1E);
|
||||
|
||||
/// 卡片阴影(深色几乎不用阴影)。
|
||||
static const cardShadow = Color(0x00000000);
|
||||
@@ -126,7 +125,7 @@ abstract final class AppColors {
|
||||
static const badgeBeige = raised;
|
||||
|
||||
/// 导航未选中。
|
||||
static const navUnselected = Color(0xFF8A8A8E);
|
||||
static const navUnselected = Color(0xFF8E8E93);
|
||||
|
||||
/// 播放器次要文字。
|
||||
static const playerMuted = Color(0xA3FFFFFF);
|
||||
|
||||
@@ -37,6 +37,7 @@ abstract final class AppDimens {
|
||||
static const double previewSectionTitle = 14;
|
||||
static const double chipFont = 10;
|
||||
static const double navLabel = 11;
|
||||
static const double navLabelGap = 4;
|
||||
|
||||
static const double buttonHeight = 48;
|
||||
static const double primaryButtonHeight = 52;
|
||||
@@ -47,8 +48,8 @@ abstract final class AppDimens {
|
||||
static const double tapTarget = 44;
|
||||
static const double contentHeaderHeight = 46;
|
||||
|
||||
static const double navHeight = 68;
|
||||
static const double navIcon = 24;
|
||||
static const double navHeight = 56;
|
||||
static const double navIcon = 26;
|
||||
static const double progressHeight = 3;
|
||||
static const double progressRadius = 2;
|
||||
static const double progressLineHeight = 3;
|
||||
|
||||
@@ -26,30 +26,17 @@ class AppBottomNavItem {
|
||||
|
||||
/// 四槽底部导航:纯黑背景,白色选中态,灰色未选中态。
|
||||
class AppBottomNav extends StatelessWidget {
|
||||
const AppBottomNav({
|
||||
super.key,
|
||||
required this.items,
|
||||
this.backgroundColor = AppColors.navBackground,
|
||||
this.barKey,
|
||||
this.dark = false,
|
||||
});
|
||||
const AppBottomNav({super.key, required this.items, this.barKey});
|
||||
|
||||
final List<AppBottomNavItem> items;
|
||||
final Color backgroundColor;
|
||||
final Key? barKey;
|
||||
final bool dark;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bar = Container(
|
||||
key: barKey,
|
||||
height: AppDimens.navHeight,
|
||||
decoration: BoxDecoration(
|
||||
color: dark ? AppColors.navBackground : backgroundColor,
|
||||
border: const Border(
|
||||
top: BorderSide(color: AppColors.navDivider, width: 0.5),
|
||||
),
|
||||
),
|
||||
color: AppColors.navBackground,
|
||||
child: Row(
|
||||
children: [
|
||||
for (final item in items)
|
||||
@@ -99,7 +86,7 @@ class _NavDestination extends StatelessWidget {
|
||||
: unselectedColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
const SizedBox(height: AppDimens.navLabelGap),
|
||||
Text(
|
||||
item.label,
|
||||
maxLines: 1,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import 'app_bottom_nav.dart';
|
||||
|
||||
/// 客户端根导航的固定顺序。
|
||||
enum AppRootTab { home, recommend, following, mine }
|
||||
@@ -36,3 +37,36 @@ extension AppRootTabUi on AppRootTab {
|
||||
AppRootTab.mine => l10n.myTab,
|
||||
};
|
||||
}
|
||||
|
||||
/// 根导航唯一入口,确保四个主页面使用完全一致的底栏配置。
|
||||
class AppRootBottomNav extends StatelessWidget {
|
||||
const AppRootBottomNav({
|
||||
super.key,
|
||||
required this.selected,
|
||||
required this.onSelected,
|
||||
this.barKey,
|
||||
});
|
||||
|
||||
final AppRootTab selected;
|
||||
final ValueChanged<AppRootTab> onSelected;
|
||||
final Key? barKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return AppBottomNav(
|
||||
barKey: barKey,
|
||||
items: [
|
||||
for (final tab in AppRootTab.values)
|
||||
AppBottomNavItem(
|
||||
tabKey: tab.tabKey,
|
||||
icon: tab.icon,
|
||||
selectedIcon: tab.selectedIcon,
|
||||
label: tab.label(l10n),
|
||||
selected: tab == selected,
|
||||
onTap: () => onSelected(tab),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@ import '../../core/formatting/error_text.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_dimens.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/app_bottom_nav.dart';
|
||||
import '../../core/widgets/app_pull_to_refresh.dart';
|
||||
import '../../core/widgets/app_root_tab.dart';
|
||||
import '../../core/widgets/catalog_debug_info.dart';
|
||||
@@ -160,7 +159,7 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
loading: logic.searchLoading,
|
||||
),
|
||||
Expanded(child: _feed(l10n, categories)),
|
||||
if (!embedded) _bottomNav(l10n),
|
||||
if (!embedded) _bottomNav(),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -612,20 +611,9 @@ class _DiscoverPageState extends State<DiscoverPage> {
|
||||
unawaited(playDramaFromCatalog(context, drama));
|
||||
}
|
||||
|
||||
Widget _bottomNav(AppLocalizations l10n) => AppBottomNav(
|
||||
items: [
|
||||
for (final tab in AppRootTab.values)
|
||||
AppBottomNavItem(
|
||||
tabKey: tab.tabKey,
|
||||
icon: tab.icon,
|
||||
selectedIcon: tab.selectedIcon,
|
||||
label: tab.label(l10n),
|
||||
selected: tab == AppRootTab.recommend,
|
||||
onTap: tab == AppRootTab.recommend
|
||||
? () {}
|
||||
: () => _act(_actionOf(tab)),
|
||||
),
|
||||
],
|
||||
Widget _bottomNav() => AppRootBottomNav(
|
||||
selected: AppRootTab.recommend,
|
||||
onSelected: (tab) => _act(_actionOf(tab)),
|
||||
);
|
||||
|
||||
DiscoverPageAction _actionOf(AppRootTab tab) => switch (tab) {
|
||||
|
||||
@@ -16,7 +16,6 @@ import '../../core/data/services/category_controller.dart';
|
||||
import '../../core/platform/haptics.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/app_bottom_nav.dart';
|
||||
import '../../core/widgets/app_root_tab.dart';
|
||||
import '../../core/widgets/catalog_debug_info.dart';
|
||||
import '../../core/widgets/catalog_endpoint_editor.dart';
|
||||
@@ -262,7 +261,6 @@ class _HomeTabsState extends State<_HomeTabs> {
|
||||
}
|
||||
|
||||
Widget _scaffold(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final feed = logic.tab == 0;
|
||||
final content = PopScope(
|
||||
canPop: false,
|
||||
@@ -275,7 +273,7 @@ class _HomeTabsState extends State<_HomeTabs> {
|
||||
backgroundColor: AppColors.transparent,
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: _body(context),
|
||||
bottomNavigationBar: logic.videoImmersive ? null : _nav(context, l10n),
|
||||
bottomNavigationBar: logic.videoImmersive ? null : _nav(context),
|
||||
),
|
||||
);
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
@@ -451,31 +449,11 @@ class _HomeTabsState extends State<_HomeTabs> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _nav(BuildContext context, AppLocalizations l10n) {
|
||||
return AppBottomNav(
|
||||
Widget _nav(BuildContext context) {
|
||||
return AppRootBottomNav(
|
||||
barKey: const Key('home-tabs-bar'),
|
||||
dark: logic.tab == 0,
|
||||
items: [
|
||||
_navItem(context, l10n, AppRootTab.home),
|
||||
_navItem(context, l10n, AppRootTab.recommend),
|
||||
_navItem(context, l10n, AppRootTab.following),
|
||||
_navItem(context, l10n, AppRootTab.mine),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
AppBottomNavItem _navItem(
|
||||
BuildContext context,
|
||||
AppLocalizations l10n,
|
||||
AppRootTab tab,
|
||||
) {
|
||||
return AppBottomNavItem(
|
||||
tabKey: tab.tabKey,
|
||||
icon: tab.icon,
|
||||
selectedIcon: tab.selectedIcon,
|
||||
label: tab.label(l10n),
|
||||
selected: logic.tab == tab.index,
|
||||
onTap: () => _selectTab(context, tab.index),
|
||||
selected: AppRootTab.values[logic.tab],
|
||||
onSelected: (tab) => _selectTab(context, tab.index),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import '../../core/data/services/wallet_controller.dart';
|
||||
import '../../core/theme/app_colors.dart';
|
||||
import '../../core/theme/app_dimens.dart';
|
||||
import '../../core/theme/app_theme.dart';
|
||||
import '../../core/widgets/app_bottom_nav.dart';
|
||||
import '../../core/widgets/app_root_tab.dart';
|
||||
import '../../core/widgets/auth_gate.dart';
|
||||
import '../../core/widgets/plain_page_scaffold.dart';
|
||||
@@ -87,7 +86,7 @@ class _MyPageState extends State<MyPage> {
|
||||
children: [
|
||||
_topBar(l10n),
|
||||
Expanded(child: _body(context, l10n)),
|
||||
if (!embedded) _bottomNav(l10n),
|
||||
if (!embedded) _bottomNav(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -238,19 +237,10 @@ class _MyPageState extends State<MyPage> {
|
||||
_openProtected(RechargePage(onPurchased: () => unawaited(wallet.load())));
|
||||
}
|
||||
|
||||
Widget _bottomNav(AppLocalizations l10n) {
|
||||
return AppBottomNav(
|
||||
items: [
|
||||
for (final tab in AppRootTab.values)
|
||||
AppBottomNavItem(
|
||||
tabKey: tab.tabKey,
|
||||
icon: tab.icon,
|
||||
selectedIcon: tab.selectedIcon,
|
||||
label: tab.label(l10n),
|
||||
selected: tab == AppRootTab.mine,
|
||||
onTap: tab == AppRootTab.mine ? () {} : () => _act(_actionOf(tab)),
|
||||
),
|
||||
],
|
||||
Widget _bottomNav() {
|
||||
return AppRootBottomNav(
|
||||
selected: AppRootTab.mine,
|
||||
onSelected: (tab) => _act(_actionOf(tab)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user