Files
monisuo/flutter_monisuo/lib/ui/pages/asset/components/holdings_section.dart
sion123 02099d2a6a docs: relocate skills system documentation and refactor asset page components
Move skills system documentation from bottom to top of CLAUDE.md for better organization. Refactor Flutter asset page by extracting UI components into separate files and updating import structure for improved modularity.
2026-04-05 22:38:56 +08:00

209 lines
6.8 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.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';
/// 持仓区域 — .pen nodes th9BG (header) + 6X6tC (card)
/// Holdings Header: "交易账户持仓" 16px w600 $text-primary | "查看全部 >" 12px normal $text-secondary
/// 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: GoogleFonts.inter(
fontSize: 16,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
Text(
'查看全部 >',
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.normal,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
// Holdings card — uses real provider.holdings data
if (holdings.isEmpty)
Padding(
padding: const EdgeInsets.all(AppSpacing.xl),
child: Text(
'暂无持仓',
style: GoogleFonts.inter(
fontSize: 13,
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: GoogleFonts.inter(
fontSize: 14,
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: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 2),
Text(
quantity,
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.normal,
color: colorScheme.onSurfaceVariant,
),
),
],
),
),
// Value + profit rate — .pen vYJsU/2nLAg/IlWck
Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: GoogleFonts.inter(
fontSize: 13,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 2),
Text(
profitRate,
style: GoogleFonts.inter(
fontSize: 12,
fontWeight: FontWeight.w500,
color: profitColor,
),
),
],
),
],
),
);
}
}