194 lines
6.1 KiB
Dart
194 lines
6.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../../core/theme/app_theme.dart';
|
|
import '../../../../core/theme/app_theme_extension.dart';
|
|
import '../../../../core/theme/app_spacing.dart';
|
|
import '../../../../data/models/account_models.dart';
|
|
import '../../../components/glass_panel.dart';
|
|
import '../../../components/coin_icon.dart';
|
|
import '../../chart/chart_page.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 accentColor = context.appColors.accentPrimary;
|
|
final accentBgColor = accentColor.withValues(alpha: 0.1);
|
|
final profitColor = isProfit ? context.appColors.up : context.appColors.down;
|
|
|
|
return InkWell(
|
|
onTap: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => ChartPage(symbol: coinCode)),
|
|
);
|
|
},
|
|
child: 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,
|
|
child: CoinIcon(
|
|
symbol: coinCode,
|
|
size: 36,
|
|
isCircle: false,
|
|
),
|
|
),
|
|
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: context.colors.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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|