feat(ui): 将待领取、可结算、已过期列表改为堆叠卡片展示

- 恢复待领取列表的堆叠卡片展示(StackedCardsView)
- 将可结算明细列表改为堆叠卡片展示
- 将已过期明细列表改为堆叠卡片展示
- 新增 _buildStackedSettleableRewardCard 方法
- 新增 _buildStackedExpiredRewardCard 方法
- 添加各列表的条目数量显示

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-17 00:13:43 -08:00
parent cceae5452a
commit 1dfa02b386
1 changed files with 411 additions and 162 deletions

View File

@ -18,8 +18,7 @@ import '../../../../routes/route_paths.dart';
import '../../../../routes/app_router.dart';
import '../../../auth/presentation/providers/auth_provider.dart';
import '../widgets/team_tree_widget.dart';
// TODO:
// import '../widgets/stacked_cards_widget.dart';
import '../widgets/stacked_cards_widget.dart';
/// -
///
@ -1843,151 +1842,145 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
],
),
const SizedBox(height: 8),
// TODO:
// StackedCardsView<PendingRewardItem>(
// items: _pendingRewards,
// peekHeight: 28,
// expandedCardHeight: 110,
// enableSound: true,
// itemBuilder: (item, isSelected, index) => _buildStackedPendingRewardCard(item, isSelected),
// ),
//
..._pendingRewards.map((item) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: _buildPendingRewardItem(item),
)),
//
StackedCardsView<PendingRewardItem>(
items: _pendingRewards,
peekHeight: 28,
expandedCardHeight: 110,
enableSound: true,
itemBuilder: (item, isSelected, index) => _buildStackedPendingRewardCard(item, isSelected),
),
],
],
);
}
// TODO:
// ///
// Widget _buildStackedPendingRewardCard(PendingRewardItem item, bool isSelected) {
// //
// final remainingSeconds = item.remainingSeconds > 0 ? item.remainingSeconds : 0;
// final hours = (remainingSeconds ~/ 3600).toString().padLeft(2, '0');
// final minutes = ((remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0');
// final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
// final countdown = '$hours:$minutes:$seconds';
//
// //
// final List<String> amountParts = [];
// if (item.usdtAmount > 0) {
// amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分');
// }
// if (item.hashpowerAmount > 0) {
// amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
// }
// final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分';
//
// return Container(
// height: isSelected ? 110 : 48,
// decoration: BoxDecoration(
// color: isSelected ? Colors.white : const Color(0xFFFFFDF8),
// borderRadius: BorderRadius.circular(8),
// border: Border.all(
// color: isSelected ? const Color(0x44D4AF37) : const Color(0x22D4AF37),
// width: isSelected ? 1.5 : 1,
// ),
// ),
// child: isSelected
// ? Padding(
// padding: const EdgeInsets.all(12),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // +
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// item.rightTypeName,
// style: const TextStyle(
// fontSize: 14,
// fontFamily: 'Inter',
// fontWeight: FontWeight.w600,
// color: Color(0xFF5D4037),
// ),
// ),
// Row(
// children: [
// const Icon(
// Icons.timer_outlined,
// size: 14,
// color: Color(0xFFD4AF37),
// ),
// const SizedBox(width: 4),
// Text(
// countdown,
// style: const TextStyle(
// fontSize: 12,
// fontFamily: 'Consolas',
// fontWeight: FontWeight.w600,
// color: Color(0xFFD4AF37),
// ),
// ),
// ],
// ),
// ],
// ),
// const SizedBox(height: 8),
// //
// Text(
// amountText,
// style: const TextStyle(
// fontSize: 16,
// fontFamily: 'Inter',
// fontWeight: FontWeight.w700,
// color: Color(0xFF5D4037),
// ),
// ),
// //
// if (item.memo.isNotEmpty) ...[
// const SizedBox(height: 4),
// Text(
// item.memo,
// style: const TextStyle(
// fontSize: 11,
// fontFamily: 'Inter',
// color: Color(0x995D4037),
// ),
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// ),
// ],
// ],
// ),
// )
// : Padding(
// //
// padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Text(
// item.rightTypeName,
// style: const TextStyle(
// fontSize: 13,
// fontFamily: 'Inter',
// fontWeight: FontWeight.w500,
// color: Color(0xFF5D4037),
// ),
// ),
// Text(
// amountText,
// style: const TextStyle(
// fontSize: 13,
// fontFamily: 'Inter',
// fontWeight: FontWeight.w600,
// color: Color(0xFFD4AF37),
// ),
// ),
// ],
// ),
// ),
// );
// }
///
Widget _buildStackedPendingRewardCard(PendingRewardItem item, bool isSelected) {
//
final remainingSeconds = item.remainingSeconds > 0 ? item.remainingSeconds : 0;
final hours = (remainingSeconds ~/ 3600).toString().padLeft(2, '0');
final minutes = ((remainingSeconds % 3600) ~/ 60).toString().padLeft(2, '0');
final seconds = (remainingSeconds % 60).toString().padLeft(2, '0');
final countdown = '$hours:$minutes:$seconds';
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分');
}
if (item.hashpowerAmount > 0) {
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
}
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分';
return Container(
height: isSelected ? 110 : 48,
decoration: BoxDecoration(
color: isSelected ? Colors.white : const Color(0xFFFFFDF8),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? const Color(0x44D4AF37) : const Color(0x22D4AF37),
width: isSelected ? 1.5 : 1,
),
),
child: isSelected
? Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// +
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
Row(
children: [
const Icon(
Icons.timer_outlined,
size: 14,
color: Color(0xFFD4AF37),
),
const SizedBox(width: 4),
Text(
countdown,
style: const TextStyle(
fontSize: 12,
fontFamily: 'Consolas',
fontWeight: FontWeight.w600,
color: Color(0xFFD4AF37),
),
),
],
),
],
),
const SizedBox(height: 8),
//
Text(
amountText,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
//
if (item.memo.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
item.memo,
style: const TextStyle(
fontSize: 11,
fontFamily: 'Inter',
color: Color(0x995D4037),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
)
: Padding(
//
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.rightTypeName,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
color: Color(0xFF5D4037),
),
),
Text(
amountText,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFFD4AF37),
),
),
],
),
),
);
}
///
Widget _buildPendingRewardItem(PendingRewardItem item) {
@ -2133,18 +2126,37 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(height: 16),
const Divider(color: Color(0x33D4AF37), height: 1),
const SizedBox(height: 12),
const Text(
'可结算明细',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'可结算明细',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
Text(
'${_settleableRewards.length}',
style: const TextStyle(
fontSize: 12,
fontFamily: 'Inter',
color: Color(0xFF8B5A2B),
),
),
],
),
const SizedBox(height: 8),
//
...(_settleableRewards.map((item) => _buildSettleableRewardItem(item))),
//
StackedCardsView<SettleableRewardItem>(
items: _settleableRewards,
peekHeight: 28,
expandedCardHeight: 90,
enableSound: true,
itemBuilder: (item, isSelected, index) => _buildStackedSettleableRewardCard(item, isSelected),
),
],
const SizedBox(height: 11),
// USDT
@ -2279,6 +2291,115 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
);
}
///
Widget _buildStackedSettleableRewardCard(SettleableRewardItem item, bool isSelected) {
//
final settledDate = '${item.settledAt.month}/${item.settledAt.day} ${item.settledAt.hour.toString().padLeft(2, '0')}:${item.settledAt.minute.toString().padLeft(2, '0')}';
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分');
}
if (item.hashpowerAmount > 0) {
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
}
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分';
return Container(
height: isSelected ? 90 : 48,
decoration: BoxDecoration(
color: isSelected ? Colors.white : const Color(0xFFFFFDF8),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? const Color(0x44D4AF37) : const Color(0x22D4AF37),
width: isSelected ? 1.5 : 1,
),
),
child: isSelected
? Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// +
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.allocationTypeName,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
Row(
children: [
const Icon(
Icons.check_circle_outline,
size: 14,
color: Color(0xFF4CAF50),
),
const SizedBox(width: 4),
Text(
settledDate,
style: const TextStyle(
fontSize: 12,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
color: Color(0xFF4CAF50),
),
),
],
),
],
),
const SizedBox(height: 8),
//
Text(
amountText,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF5D4037),
),
),
],
),
)
: Padding(
//
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.allocationTypeName,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
color: Color(0xFF5D4037),
),
),
Text(
amountText,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF4CAF50),
),
),
],
),
),
);
}
///
Widget _buildExpiredSection() {
return SizedBox(
@ -2365,18 +2486,37 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
const SizedBox(height: 16),
const Divider(color: Color(0x33D4AF37), height: 1),
const SizedBox(height: 12),
const Text(
'已过期明细',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'已过期明细',
style: TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF5D4037),
),
),
Text(
'${_expiredRewards.length}',
style: const TextStyle(
fontSize: 12,
fontFamily: 'Inter',
color: Color(0xFF8B5A2B),
),
),
],
),
const SizedBox(height: 8),
//
...(_expiredRewards.map((item) => _buildExpiredRewardItem(item))),
//
StackedCardsView<ExpiredRewardItem>(
items: _expiredRewards,
peekHeight: 28,
expandedCardHeight: 90,
enableSound: true,
itemBuilder: (item, isSelected, index) => _buildStackedExpiredRewardCard(item, isSelected),
),
],
],
),
@ -2463,6 +2603,115 @@ class _ProfilePageState extends ConsumerState<ProfilePage> {
);
}
///
Widget _buildStackedExpiredRewardCard(ExpiredRewardItem item, bool isSelected) {
//
final expiredDate = '${item.expiredAt.month}/${item.expiredAt.day} ${item.expiredAt.hour.toString().padLeft(2, '0')}:${item.expiredAt.minute.toString().padLeft(2, '0')}';
//
final List<String> amountParts = [];
if (item.usdtAmount > 0) {
amountParts.add('${_formatNumber(item.usdtAmount)} 绿积分');
}
if (item.hashpowerAmount > 0) {
amountParts.add('${_formatNumber(item.hashpowerAmount)} 算力');
}
final amountText = amountParts.isNotEmpty ? amountParts.join(' ') : '0 绿积分';
return Container(
height: isSelected ? 90 : 48,
decoration: BoxDecoration(
color: isSelected ? const Color(0xFFF5F5F5) : const Color(0xFFFAFAFA),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isSelected ? const Color(0x44999999) : const Color(0x22999999),
width: isSelected ? 1.5 : 1,
),
),
child: isSelected
? Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// +
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.allocationTypeName,
style: const TextStyle(
fontSize: 14,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFF999999),
),
),
Row(
children: [
const Icon(
Icons.cancel_outlined,
size: 14,
color: Color(0xFFE57373),
),
const SizedBox(width: 4),
Text(
expiredDate,
style: const TextStyle(
fontSize: 12,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
color: Color(0xFFE57373),
),
),
],
),
],
),
const SizedBox(height: 8),
//
Text(
amountText,
style: const TextStyle(
fontSize: 16,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
color: Color(0xFF999999),
),
),
],
),
)
: Padding(
//
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
item.allocationTypeName,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w500,
color: Color(0xFF999999),
),
),
Text(
amountText,
style: const TextStyle(
fontSize: 13,
fontFamily: 'Inter',
fontWeight: FontWeight.w600,
color: Color(0xFFE57373),
),
),
],
),
),
);
}
///
Widget _buildActionButtons() {
return Column(