Files
monisuo/flutter_monisuo/lib/ui/pages/asset/components/holdings_section.dart
2026-04-06 09:31:14 +08:00

193 lines
6.3 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../data/models/account_models.dart';
import '../../../components/glass_panel.dart';
/// 持仓区域
/// Header: "我的资产" + "查看全部 >"
/// Holdings Card: cornerRadius lg, fill $surface-card, stroke $border-default 1px
class HoldingsSection extends StatelessWidget {
final List holdings;
const HoldingsSection({super.key, required this.holdings});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Column(
children: [
// Header row: "我的资产" + "查看全部 >"
Padding(
padding: const EdgeInsets.only(bottom: AppSpacing.sm),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'我的资产',
style: AppTextStyles.headlineLarge(context),
),
Text(
'查看全部 >',
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w400,
),
),
],
),
),
// Holdings card — uses real provider.holdings data
if (holdings.isEmpty)
Padding(
padding: const EdgeInsets.all(AppSpacing.xl),
child: Text(
'暂无持仓',
style: AppTextStyles.bodyLarge(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),
)
else
GlassPanel(
padding: EdgeInsets.zero,
borderRadius: BorderRadius.circular(AppRadius.lg),
child: Column(
children: List.generate(holdings.length, (index) {
final h = holdings[index] as AccountTrade;
final isProfit = h.profitRate >= 0;
return Column(
children: [
HoldingRow(
coinCode: h.coinCode,
quantity: double.tryParse(h.quantity)?.toStringAsFixed(4) ?? h.quantity,
value: '${double.tryParse(h.currentValue)?.toStringAsFixed(2) ?? h.currentValue} USDT',
profitRate: '${isProfit ? '+' : ''}${h.profitRate.toStringAsFixed(2)}%',
isProfit: isProfit,
),
if (index < holdings.length - 1) const HoldingDivider(),
],
);
}),
),
),
],
);
}
}
/// 持仓行分隔线 — .pen node BCCbR / yejhE
/// fill: $border-default, height: 1, opacity: 0.5
class HoldingDivider extends StatelessWidget {
const HoldingDivider({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
height: 1,
margin: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
);
}
}
/// 持仓行 — matching .pen nodes dAt4j / eK6vq / jiSUK
/// padding [14, 16], space_between layout
/// Left: avatar circle (36x36, radius 18, fill $accent-light) + coin info (gap 2)
/// Right: value + pnl (gap 2, align end)
class HoldingRow extends StatelessWidget {
final String coinCode;
final String quantity;
final String value;
final String profitRate;
final bool isProfit;
const HoldingRow({
super.key,
required this.coinCode,
required this.quantity,
required this.value,
required this.profitRate,
required this.isProfit,
});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final isDark = Theme.of(context).brightness == Brightness.dark;
final accentColor = isDark ? colorScheme.secondary : colorScheme.primary;
final accentBgColor = accentColor.withValues(alpha: 0.1);
final profitColor = isProfit ? AppColorScheme.getUpColor(isDark) : AppColorScheme.getDownColor(isDark);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md, vertical: 14),
child: Row(
children: [
// Avatar circle with first letter — .pen SJNDJ/EjSIN/3GQ5M
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: accentBgColor,
borderRadius: BorderRadius.circular(18),
),
alignment: Alignment.center,
child: Text(
coinCode.substring(0, 1),
style: AppTextStyles.headlineMedium(context).copyWith(
fontWeight: FontWeight.w700,
color: accentColor,
),
),
),
const SizedBox(width: 10),
// Coin name + quantity — .pen fivxJ/Kxv3d/5CsoQ
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
coinCode,
style: AppTextStyles.headlineMedium(context),
),
const SizedBox(height: 2),
Text(
quantity,
style: AppTextStyles.bodyMedium(context).copyWith(
fontWeight: FontWeight.w400,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
// Value + profit rate — .pen vYJsU/2nLAg/IlWck
Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: AppTextStyles.numberMedium(context).copyWith(
fontSize: 13,
),
),
const SizedBox(height: 2),
Text(
profitRate,
style: AppTextStyles.numberSmall(context).copyWith(
color: profitColor,
),
),
],
),
],
),
);
}
}