feat: 改造我的页渐变与图标网格

This commit is contained in:
Zhou Weidong committed 2026-10-22 21:35:14 +08:00
1 parent e70ca03c03
commit 9785a1baf3
6 files changed
+244 -179

No files matched your search

+7
View File
@@ -84,6 +84,13 @@ abstract final class AppColors {
/// 「我的」页卡片与描边。
static const mineCard = Color(0xFF16161A);
static const mineOutline = Color(0xFF4A4A4A);
static const mineGradientTop = Color(0xFF1A0F0A);
static const mineGradientMiddle = Color(0xFF0D0908);
static const mineGradientBottom = Color(0xFF000000);
static const mineGridSurface = Color(0x99101010);
static const mineGridDivider = Color(0xFF2A2A2A);
static const mineGridIconSurface = Color(0xFF1C1C1E);
static const mineGridLabel = Color(0xFFE6E6EB);
/// 次级文字与列表 chevron。
static const textPrimary = Color(0xFFFFFFFF);
+7 -2
View File
@@ -179,11 +179,16 @@ abstract final class AppDimens {
/// 「我的」页中性视觉。
static const double mineCardRadius = 16;
static const double mineRowHeight = 56;
static const double minePageTitle = 17;
static const double mineSectionTitle = 15;
static const double mineAvatar = 56;
static const double mineUserName = 20;
static const double mineUid = 13;
static const double mineMenuIcon = 22;
static const double mineGridHorizontal = 14;
static const double mineGridRadius = 12;
static const double mineGridCellHeight = 96;
static const double mineGridIconSurface = 44;
static const double mineGridIcon = 22;
static const double mineGridLabel = 12;
static const double mineGridDividerWidth = 1;
}
+47 -31
View File
@@ -3,6 +3,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../../core/data/repositories/watch_history_repository.dart';
@@ -80,25 +81,39 @@ class _MyPageState extends State<MyPage> {
Widget _page(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final content = ColoredBox(
final content = DecoratedBox(
key: const Key('mine-page-surface'),
color: AppColors.appBackground,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.mineGradientTop,
AppColors.mineGradientMiddle,
AppColors.mineGradientBottom,
],
stops: [0, 0.48, 1],
),
),
child: SafeArea(
bottom: false,
child: Column(
children: [
_topBar(l10n),
Expanded(child: _body(context, l10n)),
Expanded(child: _body(context)),
if (!embedded) _bottomNav(),
],
),
),
);
return Theme(
data: AppTheme.dark(),
child: embedded
? content
: Scaffold(backgroundColor: AppColors.appBackground, body: content),
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.light,
child: Theme(
data: AppTheme.dark(),
child: embedded
? content
: Scaffold(backgroundColor: AppColors.appBackground, body: content),
),
);
}
@@ -125,24 +140,34 @@ class _MyPageState extends State<MyPage> {
);
}
Widget _body(BuildContext context, AppLocalizations l10n) {
final locale = context.watch<LocaleController>();
Widget _body(BuildContext context) {
return ListView(
key: const Key('mine-page-scroll'),
padding: const EdgeInsets.fromLTRB(
AppDimens.pageHorizontal,
0,
AppDimens.pageHorizontal,
AppDimens.spaceXl,
),
padding: const EdgeInsets.only(bottom: AppDimens.spaceXl),
children: [
MineHeader(onLogin: _openLogin, onEdit: _openEditActions),
MineVipBanner(onOpen: () => _openDestination(MineDestination.vip)),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
),
child: MineHeader(onLogin: _openLogin, onEdit: _openEditActions),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.pageHorizontal,
),
child: MineVipBanner(
onOpen: () => _openDestination(MineDestination.vip),
),
),
const SizedBox(height: AppDimens.spaceMd),
MineMenuList(
onOpen: _openDestination,
onLanguage: _openLanguage,
languageLabel: _languageLabel(l10n, locale.locale),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.mineGridHorizontal,
),
child: MineMenuList(
onOpen: _openDestination,
onLanguage: _openLanguage,
),
),
],
);
@@ -252,15 +277,6 @@ class _MyPageState extends State<MyPage> {
_openProtected(VipPage(onPurchased: () => unawaited(wallet.load())));
}
String _languageLabel(AppLocalizations l10n, Locale? locale) {
return switch (locale?.languageCode) {
'zh' => l10n.simplifiedChinese,
'en' => l10n.english,
'id' => l10n.bahasaIndonesia,
_ => l10n.language,
};
}
Future<void> _openLanguage() async {
final selected = await showModalBottomSheet<Locale>(
context: context,
+125 -126
View File
@@ -108,18 +108,16 @@ class MineVipBanner extends StatelessWidget {
}
}
/// 「我的」页单列功能菜单。
/// 「我的」页 4 列功能入口。
class MineMenuList extends StatelessWidget {
const MineMenuList({
super.key,
required this.onOpen,
required this.onLanguage,
required this.languageLabel,
});
final ValueChanged<MineDestination> onOpen;
final VoidCallback onLanguage;
final String languageLabel;
@override
Widget build(BuildContext context) {
@@ -127,58 +125,85 @@ class MineMenuList extends StatelessWidget {
return Container(
key: const Key('mine-menu'),
decoration: BoxDecoration(
color: AppColors.mineCard,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
border: Border.all(color: AppColors.contentBorder),
color: AppColors.mineGridSurface,
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.mineCardRadius),
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
clipBehavior: Clip.antiAlias,
child: Column(
child: Table(
key: const Key('mine-menu-grid'),
defaultColumnWidth: const FlexColumnWidth(),
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
border: const TableBorder(
horizontalInside: BorderSide(
color: AppColors.mineGridDivider,
width: AppDimens.mineGridDividerWidth,
),
verticalInside: BorderSide(
color: AppColors.mineGridDivider,
width: AppDimens.mineGridDividerWidth,
),
),
children: [
_row(
key: const Key('mine-wallet'),
icon: Icons.account_balance_wallet_outlined,
title: l10n.wallet,
destination: MineDestination.wallet,
TableRow(
children: [
_cell(
index: 0,
actionKey: const Key('mine-wallet'),
icon: Icons.account_balance_wallet_outlined,
label: l10n.wallet,
onTap: () => onOpen(MineDestination.wallet),
),
_cell(
index: 1,
actionKey: const Key('mine-downloads'),
icon: Icons.download_outlined,
label: l10n.offlineDownloads,
onTap: () => onOpen(MineDestination.downloads),
),
_cell(
index: 2,
actionKey: const Key('mine-history'),
icon: Icons.history_rounded,
label: l10n.watchHistory,
onTap: () => onOpen(MineDestination.history),
),
_cell(
index: 3,
actionKey: const Key('mine-watch-preferences'),
icon: Icons.tune_rounded,
label: l10n.watchPreferences,
onTap: () => onOpen(MineDestination.preferences),
),
],
),
_divider(),
_row(
key: const Key('mine-downloads'),
icon: Icons.download_outlined,
title: l10n.offlineDownloads,
destination: MineDestination.downloads,
),
_divider(),
_row(
key: const Key('mine-history'),
icon: Icons.history_rounded,
title: l10n.watchHistory,
destination: MineDestination.history,
),
_divider(),
_row(
key: const Key('mine-watch-preferences'),
icon: Icons.tune_rounded,
title: l10n.watchPreferences,
destination: MineDestination.preferences,
),
_divider(),
_row(
key: const Key('mine-help'),
icon: Icons.support_agent_rounded,
title: l10n.contactSupport,
destination: MineDestination.help,
),
_divider(),
_languageRow(l10n),
_divider(),
_row(
key: const Key('mine-settings'),
icon: Icons.settings_outlined,
title: l10n.settings,
destination: MineDestination.settings,
TableRow(
children: [
_cell(
index: 4,
actionKey: const Key('mine-help'),
icon: Icons.support_agent_rounded,
label: l10n.contactSupport,
onTap: () => onOpen(MineDestination.help),
),
_cell(
index: 5,
actionKey: const Key('mine-language'),
icon: Icons.language_rounded,
label: l10n.language,
onTap: onLanguage,
),
_cell(
index: 6,
actionKey: const Key('mine-settings'),
icon: Icons.settings_outlined,
label: l10n.settings,
onTap: () => onOpen(MineDestination.settings),
),
_cell(index: 7),
],
),
],
),
@@ -186,84 +211,58 @@ class MineMenuList extends StatelessWidget {
);
}
Widget _languageRow(AppLocalizations l10n) {
return ListTile(
key: const Key('mine-language'),
minTileHeight: AppDimens.mineRowHeight,
contentPadding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceLg),
leading: const Icon(
Icons.language_rounded,
color: AppColors.textPrimary,
size: AppDimens.mineMenuIcon,
),
title: Text(
l10n.language,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w500,
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
languageLabel,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: AppDimens.caption,
),
),
const SizedBox(width: AppDimens.spaceXs),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.textSecondary,
size: 18,
),
],
),
onTap: onLanguage,
);
}
Widget _row({
required Key key,
required IconData icon,
required String title,
required MineDestination destination,
Widget _cell({
required int index,
Key? actionKey,
IconData? icon,
String? label,
VoidCallback? onTap,
}) {
return ListTile(
key: key,
minTileHeight: AppDimens.mineRowHeight,
contentPadding: const EdgeInsets.symmetric(horizontal: AppDimens.spaceLg),
leading: Icon(
icon,
color: AppColors.textPrimary,
size: AppDimens.mineMenuIcon,
),
title: Text(
title,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.listTitle,
fontWeight: FontWeight.w500,
),
),
trailing: const Icon(
Icons.chevron_right_rounded,
color: AppColors.textSecondary,
size: 18,
),
onTap: () => onOpen(destination),
);
}
Widget _divider() {
return const Divider(
height: 1,
indent: AppDimens.spaceLg,
endIndent: AppDimens.spaceLg,
color: AppColors.contentSurface,
return SizedBox(
key: Key('mine-menu-cell-$index'),
height: AppDimens.mineGridCellHeight,
child: actionKey == null
? const SizedBox.shrink()
: InkWell(
key: actionKey,
onTap: onTap,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: AppDimens.mineGridIconSurface,
height: AppDimens.mineGridIconSurface,
decoration: const BoxDecoration(
color: AppColors.mineGridIconSurface,
shape: BoxShape.circle,
),
child: Icon(
icon,
color: AppColors.primary,
size: AppDimens.mineGridIcon,
),
),
const SizedBox(height: AppDimens.spaceXs + 2),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Text(
label ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.mineGridLabel,
fontSize: AppDimens.mineGridLabel,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
),
],
),
),
),
);
}
}