feat: 改造我的页渐变与图标网格
This commit is contained in:
1 parent
e70ca03c03
commit
9785a1baf3
6 files changed
+244
-179
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user