Files
ReelShort/lib/pages/mine/widgets/mine_menu.dart
T

390 lines
11 KiB
Dart

// Author: fruit.com.cn
import 'package:flutter/material.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../l10n/app_localizations.dart';
const double _vipMaxTextScale = 1.15;
const double _vipGradientMiddleStop = 0.48;
const double _vipGlowRadius = 0.9;
/// 「我的」页高级 VIP 卡:品牌信息、会员徽章、开通按钮与横排权益。
class MineVipBanner extends StatelessWidget {
const MineVipBanner({super.key, required this.onOpen});
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Container(
key: const Key('mine-vip'),
decoration: _decoration(),
child: Material(
color: AppColors.transparent,
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onOpen,
child: MediaQuery.withClampedTextScaling(
maxScaleFactor: _vipMaxTextScale,
child: Stack(
children: [
const Positioned.fill(child: _VipAmbientGlow()),
Padding(
padding: const EdgeInsets.all(AppDimens.mineVipPadding),
child: _VipContent(l10n: l10n, onOpen: onOpen),
),
],
),
),
),
),
);
}
BoxDecoration _decoration() {
return BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.vipCardStart,
AppColors.vipCardMiddle,
AppColors.vipCardEnd,
],
stops: [0, _vipGradientMiddleStop, 1],
),
borderRadius: BorderRadius.circular(AppDimens.mineVipRadius),
border: Border.all(color: AppColors.vipCardBorder),
boxShadow: const [
BoxShadow(
color: AppColors.vipCardRedGlow,
blurRadius: AppDimens.spaceXl,
offset: Offset(0, AppDimens.spaceSm),
),
BoxShadow(
color: AppColors.shadowSoft,
blurRadius: AppDimens.spaceLg,
offset: Offset(0, AppDimens.spaceSm),
),
],
);
}
}
class _VipContent extends StatelessWidget {
const _VipContent({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _VipCopy(l10n: l10n)),
const SizedBox(width: AppDimens.spaceSm),
_VipAction(l10n: l10n, onOpen: onOpen),
],
),
const SizedBox(height: AppDimens.spaceSm),
const Divider(height: 1, color: AppColors.vipBenefitBorder),
const SizedBox(height: AppDimens.spaceSm),
_VipBenefits(l10n: l10n),
],
);
}
}
class _VipCopy extends StatelessWidget {
const _VipCopy({required this.l10n});
final AppLocalizations l10n;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_VipPill(label: l10n.vipMembership),
const SizedBox(height: AppDimens.spaceSm),
Text(
l10n.vipBrandName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardText,
fontSize: AppDimens.mineVipTitleFont,
fontWeight: FontWeight.w700,
letterSpacing: -0.4,
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
l10n.mineVipSubtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipSubtitleFont,
height: 1.3,
),
),
],
);
}
}
class _VipPill extends StatelessWidget {
const _VipPill({required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(
horizontal: AppDimens.spaceSm,
vertical: AppDimens.spaceXs,
),
decoration: BoxDecoration(
color: AppColors.vipBadgeSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipGoldStart,
fontSize: AppDimens.chipFont,
fontWeight: FontWeight.w700,
letterSpacing: 1.1,
),
),
);
}
}
class _VipAction extends StatelessWidget {
const _VipAction({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
return SizedBox(
width: AppDimens.mineVipActionWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const _VipEmblem(),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
),
);
}
}
class _VipEmblem extends StatelessWidget {
const _VipEmblem();
@override
Widget build(BuildContext context) {
return Container(
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipEmblem,
height: AppDimens.mineVipEmblem,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.vipGoldStart, AppColors.vipGoldEnd],
),
border: Border.all(color: AppColors.vipCardText),
boxShadow: const [
BoxShadow(color: AppColors.vipCardGoldGlow, blurRadius: 18),
],
),
child: const Icon(
Icons.workspace_premium_rounded,
color: AppColors.vipCardStart,
size: AppDimens.mineVipEmblemIcon,
),
);
}
}
class _VipOpenButton extends StatelessWidget {
const _VipOpenButton({required this.label, required this.onOpen});
final String label;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
return SizedBox(
key: const Key('mine-vip-open'),
width: AppDimens.mineVipActionWidth,
height: AppDimens.mineVipButtonHeight,
child: DecoratedBox(
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [AppColors.vipButtonStart, AppColors.vipButtonEnd],
),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
boxShadow: const [
BoxShadow(color: AppColors.vipCardRedGlow, blurRadius: 14),
],
),
child: Material(
color: AppColors.transparent,
child: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Center(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
),
),
),
),
),
),
);
}
}
class _VipBenefits extends StatelessWidget {
const _VipBenefits({required this.l10n});
final AppLocalizations l10n;
@override
Widget build(BuildContext context) {
final items = [
(Icons.all_inclusive_rounded, l10n.vipBenefitFree),
(Icons.block_rounded, l10n.vipBenefitNoAds),
(Icons.hd_outlined, l10n.vipBenefitHd),
(Icons.download_for_offline_outlined, l10n.vipBenefitFastCache),
];
return Row(
key: const Key('mine-vip-benefits'),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var index = 0; index < items.length; index++)
Expanded(
child: _VipBenefit(
itemKey: Key('mine-vip-benefit-$index'),
icon: items[index].$1,
label: items[index].$2,
),
),
],
);
}
}
class _VipBenefit extends StatelessWidget {
const _VipBenefit({
required this.itemKey,
required this.icon,
required this.label,
});
final Key itemKey;
final IconData icon;
final String label;
@override
Widget build(BuildContext context) {
return Column(
key: itemKey,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: AppDimens.mineVipBenefitBox,
height: AppDimens.mineVipBenefitBox,
decoration: BoxDecoration(
color: AppColors.vipBenefitSurface,
border: Border.all(color: AppColors.vipBenefitBorder),
borderRadius: BorderRadius.circular(
AppDimens.mineVipBenefitBoxRadius,
),
),
child: Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
),
const SizedBox(height: AppDimens.mineVipBenefitRowGap),
Text(
label,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
],
);
}
}
class _VipAmbientGlow extends StatelessWidget {
const _VipAmbientGlow();
@override
Widget build(BuildContext context) {
return Stack(
children: const [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment(0.85, -0.8),
radius: _vipGlowRadius,
colors: [AppColors.vipCardGoldGlow, AppColors.transparent],
),
),
),
),
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
gradient: RadialGradient(
center: Alignment(-0.9, 1),
radius: _vipGlowRadius,
colors: [AppColors.vipCardRedGlow, AppColors.transparent],
),
),
),
),
],
);
}
}