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

332 lines
8.7 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';
const double _vipMaxTextScale = 1.15;
const double _vipGradientMiddleStop = 0.62;
const double _vipGlowRadius = 0.9;
const double _vipButtonLetterSpacing = 0.2;
/// 「我的」页 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: AppPressScale(
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),
boxShadow: const [
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.center,
children: [
Expanded(
child: _VipCopy(l10n: l10n, onOpen: onOpen),
),
const SizedBox(width: AppDimens.spaceSm),
const _VipArt(),
],
),
const SizedBox(height: AppDimens.spaceXs),
_VipBenefits(l10n: l10n),
],
);
}
}
class _VipCopy extends StatelessWidget {
const _VipCopy({required this.l10n, required this.onOpen});
final AppLocalizations l10n;
final VoidCallback onOpen;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.vipBrandName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardText,
fontSize: AppDimens.mineVipTitleFont,
fontWeight: FontWeight.w800,
letterSpacing: 1.0,
),
),
const SizedBox(height: AppDimens.spaceXs),
Text(
l10n.mineVipSubtitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipSubtitleFont,
height: 1.3,
),
),
const SizedBox(height: AppDimens.spaceXs),
_VipOpenButton(label: l10n.vipOpenNow, onOpen: onOpen),
],
);
}
}
class _VipArt extends StatelessWidget {
const _VipArt();
@override
Widget build(BuildContext context) {
return SizedBox(
key: const Key('mine-vip-art'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
child: Image.asset(
'assets/icon/vip_membership_badge.png',
key: const Key('mine-vip-emblem'),
width: AppDimens.mineVipArtWidth,
height: AppDimens.mineVipArtHeight,
fit: BoxFit.contain,
),
);
}
}
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: InkWell(
onTap: onOpen,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Flexible(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipGoldStart,
fontSize: AppDimens.body,
fontWeight: FontWeight.w700,
letterSpacing: _vipButtonLetterSpacing,
),
),
),
const SizedBox(width: AppDimens.spaceXs),
const Icon(
Icons.arrow_forward_rounded,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
),
],
),
),
);
}
}
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 Column(
key: const Key('mine-vip-benefits'),
children: [
_row(items, 0),
const SizedBox(height: AppDimens.spaceXs),
_row(items, 2),
],
);
}
Widget _row(List<(IconData, String)> items, int start) {
return Row(
children: [
for (var index = start; index < start + 2; 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 Row(
key: itemKey,
children: [
_VipBenefitGlyph(icon: icon),
const SizedBox(width: AppDimens.spaceXs),
Expanded(
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.vipCardMuted,
fontSize: AppDimens.mineVipBenefitFont,
fontWeight: FontWeight.w500,
height: 1.15,
),
),
),
],
);
}
}
class _VipBenefitGlyph extends StatelessWidget {
const _VipBenefitGlyph({required this.icon});
final IconData icon;
@override
Widget build(BuildContext context) {
return Icon(
icon,
color: AppColors.vipGoldStart,
size: AppDimens.mineVipBenefitGlyph,
);
}
}
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],
),
),
),
),
],
);
}
}