Files
ReelShort/lib/pages/mine/widgets/mine_action_grid.dart

274 lines
7.3 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/app_press_scale.dart';
import '../../../l10n/app_localizations.dart';
import '../mine_types.dart';
const int _gridColumnCount = 4;
const double _gridMaxTextScale = 1.2;
/// 「我的」页统一操作宫格:常用入口与服务入口按 4×2 排列。
class MineActionGrid extends StatelessWidget {
const MineActionGrid({
super.key,
required this.primaryActions,
required this.serviceActions,
});
final MinePrimaryActions primaryActions;
final MineServiceActions serviceActions;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final items = [..._primaryItems(l10n), ..._serviceItems(l10n)];
return SizedBox(
height: AppDimens.mineGridHeight,
child: DecoratedBox(
key: const Key('mine-menu'),
decoration: BoxDecoration(
color: AppColors.mineGridSurface,
border: Border.all(color: AppColors.glassHairline),
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(AppDimens.mineGridRadius),
child: MediaQuery.withClampedTextScaling(
maxScaleFactor: _gridMaxTextScale,
child: Column(
children: [
_row(items.take(_gridColumnCount).toList()),
const _MineHorizontalDivider(),
_row(items.skip(_gridColumnCount).toList()),
],
),
),
),
),
);
}
List<_MineActionItem> _primaryItems(AppLocalizations l10n) {
return [
_MineActionItem(
key: const Key('mine-wallet'),
icon: Icons.account_balance_wallet_outlined,
label: l10n.wallet,
onTap: primaryActions.onWallet,
),
_MineActionItem(
key: const Key('mine-favorites'),
icon: Icons.star_outline_rounded,
label: l10n.myFavorites,
onTap: primaryActions.onFavorites,
),
_MineActionItem(
key: const Key('mine-downloads'),
icon: Icons.download_for_offline_outlined,
label: l10n.offlineDownloads,
onTap: primaryActions.onDownloads,
),
_MineActionItem(
key: const Key('mine-history'),
icon: Icons.history_rounded,
label: l10n.watchHistory,
onTap: primaryActions.onHistory,
),
];
}
List<_MineActionItem> _serviceItems(AppLocalizations l10n) {
return [
_MineActionItem(
key: const Key('mine-help'),
icon: Icons.support_agent_outlined,
label: l10n.contactSupport,
onTap: () => serviceActions.onOpen(MineDestination.help),
),
_MineActionItem(
key: const Key('mine-language'),
icon: Icons.language_rounded,
label: l10n.language,
onTap: serviceActions.onLanguage,
),
_MineActionItem(
key: const Key('mine-settings'),
icon: Icons.settings_outlined,
label: l10n.settings,
onTap: () => serviceActions.onOpen(MineDestination.settings),
),
_MineActionItem(
key: const Key('mine-notices'),
icon: Icons.notifications_none_rounded,
label: l10n.notices,
onTap: () => serviceActions.onOpen(MineDestination.notices),
),
];
}
Widget _row(List<_MineActionItem> items) {
return SizedBox(
height: AppDimens.mineGridCellHeight,
child: Row(
children: [
for (var index = 0; index < items.length; index++) ...[
Expanded(child: _MineActionCell(item: items[index])),
if (index < items.length - 1) const _MineVerticalDivider(),
],
],
),
);
}
}
/// 宫格第一排的常用功能回调。
class MinePrimaryActions {
const MinePrimaryActions({
required this.onWallet,
required this.onFavorites,
required this.onDownloads,
required this.onHistory,
});
final VoidCallback onWallet;
final VoidCallback onFavorites;
final VoidCallback onDownloads;
final VoidCallback onHistory;
}
/// 宫格第二排的服务功能回调。
class MineServiceActions {
const MineServiceActions({required this.onOpen, required this.onLanguage});
final ValueChanged<MineDestination> onOpen;
final VoidCallback onLanguage;
}
class _MineActionItem {
const _MineActionItem({
required this.key,
required this.icon,
required this.label,
required this.onTap,
});
final Key key;
final IconData icon;
final String label;
final VoidCallback onTap;
Key get iconSurfaceKey =>
ValueKey('${(key as ValueKey<String>).value}-icon-surface');
}
class _MineActionCell extends StatelessWidget {
const _MineActionCell({required this.item});
final _MineActionItem item;
@override
Widget build(BuildContext context) {
return Material(
color: AppColors.transparent,
child: InkWell(
key: item.key,
onTap: item.onTap,
child: AppPressScale(
child: Semantics(
button: true,
label: item.label,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_iconSurface(),
const SizedBox(height: AppDimens.spaceSm),
_label(),
],
),
),
),
),
);
}
Widget _iconSurface() {
return Container(
key: item.iconSurfaceKey,
width: AppDimens.mineGridIconSurface,
height: AppDimens.mineGridIconSurface,
decoration: BoxDecoration(
gradient: const RadialGradient(
center: Alignment(-0.35, -0.45),
radius: 0.95,
colors: [
AppColors.mineOutline,
AppColors.contentChip,
AppColors.mineGridIconSurface,
],
),
shape: BoxShape.circle,
border: Border.all(color: AppColors.glassHairline),
boxShadow: [
const BoxShadow(
color: AppColors.shadowSoft,
blurRadius: AppDimens.spaceSm,
offset: Offset(0, AppDimens.spaceXs),
),
BoxShadow(
color: AppColors.brandRed.withValues(alpha: 0.12),
blurRadius: AppDimens.spaceSm,
),
],
),
child: Icon(
item.icon,
color: AppColors.mineGridIconAccent,
size: AppDimens.mineGridIcon,
),
);
}
Widget _label() {
return Text(
item.label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.mineGridLabel,
fontSize: AppDimens.mineGridLabel,
fontWeight: FontWeight.w500,
height: AppDimens.mineGridLabelLineHeight,
),
);
}
}
class _MineVerticalDivider extends StatelessWidget {
const _MineVerticalDivider();
@override
Widget build(BuildContext context) {
return Container(
width: AppDimens.mineGridHairline,
color: AppColors.mineGridDivider,
);
}
}
class _MineHorizontalDivider extends StatelessWidget {
const _MineHorizontalDivider();
@override
Widget build(BuildContext context) {
return Container(
height: AppDimens.mineGridHairline,
color: AppColors.mineGridDivider,
);
}
}