import 'package:flutter/material.dart'; import '../../../core/l10n/app_localizations.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/utils/number_format.dart'; import '../../../providers/futures_provider.dart'; /// 仓位详情页 class PositionDetailScreen extends StatelessWidget { const PositionDetailScreen({ super.key, required this.position, required this.symbol, }); final FuturesPosition position; final String symbol; String _baseCoin(String sym) { if (sym.contains('/')) return sym.split('/').first; return sym.toUpperCase().replaceFirst(RegExp(r'USDT$'), ''); } String _rawNum(double v) { if (v == v.truncateToDouble()) return v.toInt().toString(); return v.toString(); } @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; final isLong = position.side == OrderSide.long; final pnlColor = position.unrealizedPnl >= 0 ? AppColors.rise : AppColors.fall; final realizedColor = position.realizedPnl >= 0 ? AppColors.rise : AppColors.fall; final coinSymbol = _baseCoin(position.symbol); final l10n = AppLocalizations.of(context)!; final scaffoldBg = isDark ? AppColors.darkBg : AppColors.lightBgSecondary; final sideColor = isLong ? AppColors.rise : AppColors.fall; return Scaffold( backgroundColor: scaffoldBg, appBar: AppBar( elevation: 0, centerTitle: true, backgroundColor: isDark ? AppColors.darkBgSecondary : AppColors.lightBg, title: Text( l10n.positionDetail, style: TextStyle( color: cs.onSurface, fontSize: 16, fontWeight: FontWeight.w700), ), ), body: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── 标的行 ──────────────────────────────────────── Container( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBg, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ // 多/空 chip Container( padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2), decoration: BoxDecoration( color: sideColor, borderRadius: BorderRadius.circular(4), ), child: Text( isLong ? l10n.openLong : l10n.openShort, style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w700), ), ), const SizedBox(width: 6), Expanded( child: Row( children: [ Flexible( child: Text( position.symbol, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: cs.onSurface, fontSize: 15, fontWeight: FontWeight.w700), ), ), const SizedBox(width: 6), _SmTag( text: position.marginMode, color: switch (position.marginMode) { '分仓' || '逐仓' => AppColors.rankPurple, _ => AppColors.tagBlue, }, ), const SizedBox(width: 4), _LevTag(leverage: position.leverage.toInt()), ], ), ), ], ), ), const SizedBox(height: 8), // ── 价格 ────────────────────────────────────────── _InfoCard(rows: [ _InfoRowData(l10n.openAvgPrice, _rawNum(position.entryPrice)), _InfoRowData(l10n.markPrice, _rawNum(position.markPrice), isLast: true), ]), const SizedBox(height: 8), // ── 持仓数据 ────────────────────────────────────── _InfoCard(rows: [ _InfoRowData('${l10n.positionSize}($coinSymbol)', _rawNum(position.size)), _InfoRowData('${l10n.positionValue}(USDT)', formatAmount(position.size * position.markPrice)), _InfoRowData( l10n.estimatedLiqPrice, position.liquidationPrice > 0 ? _rawNum(position.liquidationPrice) : '--', valueColor: position.liquidationPrice > 0 ? AppColors.fall : null, ), _InfoRowData('${l10n.marginLabel}(USDT)', formatAmount(position.margin), isLast: true), ]), const SizedBox(height: 8), // ── 盈亏 ────────────────────────────────────────── _InfoCard(rows: [ _InfoRowData(l10n.profitRateLabel, '${formatAmount(position.roe)}%', valueColor: pnlColor), _InfoRowData(l10n.unrealizedPnl, formatAmount(position.unrealizedPnl), valueColor: pnlColor), _InfoRowData(l10n.realizedPnl, formatAmount(position.realizedPnl), valueColor: realizedColor), _InfoRowData( '${l10n.fee}(USDT)', position.commissionFee > 0 ? formatAmount(position.commissionFee) : '--', isLast: true, ), ]), const SizedBox(height: 16), ], ), ), ); } } // ── 共用 widgets ─────────────────────────────────────────────── class _SmTag extends StatelessWidget { const _SmTag({required this.text, this.color}); final String text; final Color? color; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; final c = color; if (c != null) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: c.withAlpha(isDark ? 45 : 25), borderRadius: BorderRadius.circular(4), ), child: Text(text, style: TextStyle( color: c, fontSize: 11, fontWeight: FontWeight.w600)), ); } return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: cs.onSurface.withAlpha(12), border: Border.all(color: cs.outline.withAlpha(80)), borderRadius: BorderRadius.circular(4), ), child: Text(text, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 11, fontWeight: FontWeight.w600)), ); } } class _LevTag extends StatelessWidget { const _LevTag({required this.leverage}); final int leverage; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: AppColors.leverageGoldBg, borderRadius: BorderRadius.circular(4), ), child: Text('${leverage}X', style: const TextStyle( color: AppColors.leverageGold, fontSize: 11, fontWeight: FontWeight.w700)), ); } } class _InfoRowData { const _InfoRowData(this.label, this.value, {this.valueColor, this.isLast = false}); final String label; final String value; final Color? valueColor; final bool isLast; } class _InfoCard extends StatelessWidget { const _InfoCard({required this.rows}); final List<_InfoRowData> rows; @override Widget build(BuildContext context) { final cs = Theme.of(context).colorScheme; final isDark = Theme.of(context).brightness == Brightness.dark; return Container( color: isDark ? AppColors.darkBgSecondary : AppColors.lightBg, child: Column( children: rows.map((r) { return Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13), child: Row( children: [ Expanded( child: Text(r.label, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: cs.onSurface.withAlpha(153), fontSize: 13)), ), const SizedBox(width: 8), Text(r.value, textAlign: TextAlign.end, style: TextStyle( color: r.valueColor ?? cs.onSurface, fontSize: 13, fontWeight: FontWeight.w500)), ], ), ), if (!r.isLast) Divider( height: 1, thickness: 0.5, indent: 16, endIndent: 16, color: cs.outline.withAlpha(60)), ], ); }).toList(), ), ); } }