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.
209 lines
6.8 KiB
Dart
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|