Files
ReelShort/lib/pages/reward/reward_checkin_card.dart
T

274 lines
8.1 KiB
Dart

// Author: fruit.com.cn
import 'dart:async';
import 'package:flutter/material.dart';
import '../../core/data/models/reward.dart';
import '../../core/data/services/reward_controller.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_dimens.dart';
import '../../l10n/app_localizations.dart';
import 'reward_visuals.dart';
const double _cardRadius = 14;
const double _cardBorderWidth = 1;
const double _titleFont = 20;
const double _boostFont = 12;
const double _subtitleFont = 13;
const double _dayHeight = 80;
const double _dayRadius = 9;
const double _dayGap = 5;
const double _dayRewardFont = 13;
const double _dayLabelFont = 11;
const double _dayCoinSize = 24;
const double _buttonHeight = 48;
const double _buttonFont = 16;
const double _loadingSize = 20;
const double _loadingStroke = 2;
const Color _cardStart = Color(0xFF1A1A1C);
const Color _cardEnd = Color(0xFF101010);
const Color _cardBorder = Color(0xFF3A3A3C);
const Color _daySurface = Color(0xFF202022);
const Color _dayBorder = Color(0xFF363638);
const Color _currentSurfaceStart = Color(0xFF3E0813);
const Color _currentSurfaceEnd = Color(0xFF24070D);
const Color _currentBorder = Color(0xFFFF4860);
const Color _buttonEnd = Color(0xFFFF565B);
/// 奖励中心签到卡,展示七日规则与签到操作。
class RewardCheckinCard extends StatelessWidget {
const RewardCheckinCard({
super.key,
required this.l10n,
required this.rewards,
required this.checkin,
required this.onCheckIn,
required this.onBoost,
});
final AppLocalizations l10n;
final RewardController rewards;
final RewardCheckinState checkin;
final Future<void> Function() onCheckIn;
final VoidCallback onBoost;
@override
Widget build(BuildContext context) {
return Container(
key: const Key('reward-checkin-card'),
padding: const EdgeInsets.all(AppDimens.cardPadding),
decoration: BoxDecoration(
gradient: const LinearGradient(colors: [_cardStart, _cardEnd]),
borderRadius: BorderRadius.circular(_cardRadius),
border: Border.all(color: _cardBorder, width: _cardBorderWidth),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_header(),
const SizedBox(height: AppDimens.spaceXs),
Text(
l10n.rewardCheckInSubtitle,
style: const TextStyle(
color: AppColors.textSecondary,
fontSize: _subtitleFont,
),
),
const SizedBox(height: AppDimens.spaceLg),
_days(),
const SizedBox(height: AppDimens.spaceLg),
_action(),
],
),
);
}
Widget _header() {
return Row(
children: [
Expanded(
child: Text(
l10n.rewardCheckInTitle,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _titleFont,
fontWeight: FontWeight.w800,
),
),
),
InkWell(
onTap: onBoost,
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: AppDimens.spaceSm),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.rewardBoostCheckIn,
style: const TextStyle(
color: AppColors.brandRed,
fontSize: _boostFont,
fontWeight: FontWeight.w600,
),
),
const Icon(
Icons.chevron_right_rounded,
color: AppColors.brandRed,
size: 18,
),
],
),
),
),
],
);
}
Widget _days() {
if (checkin.days.isEmpty) return const SizedBox.shrink();
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var index = 0; index < checkin.days.length; index++) ...[
Expanded(
child: _RewardDay(l10n: l10n, day: checkin.days[index]),
),
if (index < checkin.days.length - 1) const SizedBox(width: _dayGap),
],
],
);
}
Widget _action() {
final disabled = checkin.checkedInToday || rewards.checkingIn;
return Container(
key: const Key('reward-checkin-action'),
height: _buttonHeight,
decoration: BoxDecoration(
gradient: disabled
? const LinearGradient(
colors: [AppColors.contentChip, AppColors.contentChip],
)
: const LinearGradient(colors: [AppColors.brandRed, _buttonEnd]),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
),
child: Material(
color: AppColors.transparent,
child: InkWell(
onTap: disabled ? null : () => unawaited(onCheckIn()),
borderRadius: BorderRadius.circular(AppDimens.pillRadius),
child: Center(child: _actionContent()),
),
),
);
}
Widget _actionContent() {
if (rewards.checkingIn) {
return const SizedBox.square(
dimension: _loadingSize,
child: CircularProgressIndicator(
strokeWidth: _loadingStroke,
color: AppColors.textPrimary,
),
);
}
final label = checkin.checkedInToday
? l10n.rewardCheckedIn
: l10n.rewardCheckInActionWithCoins(_currentReward());
return Row(
key: const Key('reward-checkin-action-content'),
mainAxisSize: MainAxisSize.min,
children: [
if (!checkin.checkedInToday) ...[
const RewardAdIcon(),
const SizedBox(width: AppDimens.spaceMd),
],
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 220),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
color: AppColors.textPrimary,
fontSize: _buttonFont,
fontWeight: FontWeight.w700,
),
),
),
],
);
}
int _currentReward() {
for (final day in checkin.days) {
if (day.current) return day.rewardCoin;
}
return checkin.days.isEmpty ? 0 : checkin.days.first.rewardCoin;
}
}
class _RewardDay extends StatelessWidget {
const _RewardDay({required this.l10n, required this.day});
final AppLocalizations l10n;
final RewardCheckinDay day;
@override
Widget build(BuildContext context) {
return Container(
height: _dayHeight,
decoration: BoxDecoration(
gradient: day.current
? const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_currentSurfaceStart, _currentSurfaceEnd],
)
: null,
color: day.current ? null : _daySurface,
borderRadius: BorderRadius.circular(_dayRadius),
border: Border.all(color: day.current ? _currentBorder : _dayBorder),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
'+${day.rewardCoin}',
style: TextStyle(
color: day.current
? const Color(0xFFFFE18C)
: AppColors.textPrimary,
fontSize: _dayRewardFont,
fontWeight: FontWeight.w800,
),
),
),
const SizedBox(height: AppDimens.spaceXs),
const RewardCoinIcon(size: _dayCoinSize),
const SizedBox(height: AppDimens.spaceXs),
FittedBox(
fit: BoxFit.scaleDown,
child: Text(
day.current
? l10n.rewardToday
: l10n.rewardDayLabel(day.dayIndex),
style: TextStyle(
color: day.checked
? AppColors.success
: AppColors.textSecondary,
fontSize: _dayLabelFont,
),
),
),
],
),
);
}
}