Files
Finlytic/FinlyticApp/lib/features/bot/widgets/bot_position_card.dart
T

201 lines
7.5 KiB
Dart

import 'package:flutter/material.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/widgets/asset_logo_widget.dart';
import '../../../core/widgets/glass_container.dart';
import '../models/bot_models.dart';
class BotPositionCard extends StatelessWidget {
final BotTradeOrderModel position;
final VoidCallback? onClosePressed;
const BotPositionCard({
super.key,
required this.position,
this.onClosePressed,
});
@override
Widget build(BuildContext context) {
final isLong = position.isLong;
final isProfitable = position.unrealizedPnlEur >= 0;
final pnlPercent = position.pnlPercent;
final rMult = position.rMultiple;
return GlassContainer(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AssetLogoWidget(
symbolOrName: position.symbol.isNotEmpty ? position.symbol : position.isin,
imageUrl: '/api/v1/logo/${position.isin}',
size: 36,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
position.symbol,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: Colors.white),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: isLong ? AppTheme.primaryEmerald.withValues(alpha: 0.2) : AppTheme.accentRed.withValues(alpha: 0.2),
),
child: Text(
position.direction.toUpperCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: isLong ? AppTheme.primaryEmerald : AppTheme.accentRed,
),
),
),
const SizedBox(width: 6),
_buildVenueBadge(position.venue),
],
),
const SizedBox(height: 2),
Text(
position.isin,
style: TextStyle(color: AppTheme.textMuted, fontSize: 11),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
'${isProfitable ? '+' : ''}${position.unrealizedPnlEur.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: isProfitable ? AppTheme.primaryEmerald : AppTheme.accentRed,
),
),
Text(
'${isProfitable ? '+' : ''}${pnlPercent.toStringAsFixed(2)}% (${rMult >= 0 ? '+' : ''}${rMult.toStringAsFixed(1)}R)',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: isProfitable ? AppTheme.primaryEmerald : AppTheme.accentRed,
),
),
],
),
],
),
const SizedBox(height: 12),
const Divider(height: 1, color: Colors.white10),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildPriceInfo('Entry / Buy-In', '${position.averageBuyIn > 0 ? position.averageBuyIn.toStringAsFixed(2) : position.entryPrice.toStringAsFixed(2)}'),
_buildPriceInfo('Current Price', '${position.currentPrice.toStringAsFixed(2)}'),
_buildPriceInfo('Stop Loss', '${position.currentStopLoss.toStringAsFixed(2)}'),
_buildPriceInfo('Take Profit 1', '${position.takeProfit1.toStringAsFixed(2)}'),
],
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildDynamicStateBadge(position),
if (position.isActive && onClosePressed != null)
TextButton.icon(
onPressed: onClosePressed,
icon: Icon(Icons.close, size: 14, color: AppTheme.accentRed),
label: Text('Glattstellen', style: TextStyle(fontSize: 12, color: AppTheme.accentRed)),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
visualDensity: VisualDensity.compact,
),
),
],
),
],
),
);
}
Widget _buildPriceInfo(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(color: AppTheme.textMuted, fontSize: 10)),
const SizedBox(height: 2),
Text(value, style: const TextStyle(color: Colors.white70, fontSize: 12, fontWeight: FontWeight.w600)),
],
);
}
Widget _buildVenueBadge(String venue) {
final isAlpaca = venue.toLowerCase().contains('alpaca');
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: isAlpaca ? Colors.blue.withValues(alpha: 0.15) : Colors.purple.withValues(alpha: 0.15),
),
child: Text(
isAlpaca ? 'Alpaca US' : 'Synthetic KO',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: isAlpaca ? Colors.lightBlueAccent : Colors.purpleAccent,
),
),
);
}
Widget _buildDynamicStateBadge(BotTradeOrderModel position) {
String text = 'Pending TP1';
Color color = Colors.amber;
if (position.isBreakEven) {
text = 'Free-Roll Active (BE)';
color = AppTheme.primaryEmerald;
} else if (position.isTp1Hit) {
text = 'TP1 Hit (Trailing Active)';
color = Colors.cyanAccent;
} else if (position.isClosed) {
text = 'Closed';
color = Colors.grey;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: color.withValues(alpha: 0.15),
border: Border.all(color: color.withValues(alpha: 0.4), width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.bolt, size: 12, color: color),
const SizedBox(width: 4),
Text(
text,
style: TextStyle(color: color, fontSize: 11, fontWeight: FontWeight.bold),
),
],
),
);
}
}