feat(trades): add live execution cockpit, closing cockpit, calculation cards and precision trade settings

This commit is contained in:
2026-08-15 19:30:25 +02:00
parent 882d24a316
commit 34fa774cbf
31 changed files with 4235 additions and 630 deletions
@@ -3,17 +3,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../core/network/api_client.dart';
import '../../../core/network/signalr_service.dart';
import '../../../core/theme/app_theme.dart';
import '../../auth/bloc/auth_bloc.dart';
import '../bloc/trade_bloc.dart';
import '../bloc/trade_event.dart';
import '../bloc/trade_state.dart';
import '../models/trade_model.dart';
import '../models/trade_acceptance_dto.dart';
import '../models/close_trade_request_dto.dart';
import '../repositories/trade_repository.dart';
import '../widgets/trade_card.dart';
import '../widgets/proposed_auto_trades_card.dart';
import '../widgets/trade_acceptance_dialog.dart';
import '../widgets/trade_execution_dialog.dart';
import '../widgets/trade_execution_cockpit.dart';
import '../widgets/trade_closing_cockpit.dart';
import '../widgets/trade_performance_bar.dart';
class TradesFeedScreen extends StatelessWidget {
@@ -55,28 +54,45 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
super.dispose();
}
Future<void> _handleAcceptProposal(BuildContext context, TradeModel trade) async {
final authState = context.read<AuthBloc>().state;
final currentUserId = (authState is Authenticated) ? authState.user.userId : 'default_user';
void _handleAcceptProposal(BuildContext context, TradeModel trade, {bool isActive = false}) {
final tradeBloc = context.read<TradeBloc>();
final result = await showDialog<TradeAcceptanceDto>(
context: context,
builder: (ctx) => TradeAcceptanceDialog(
trade: trade,
theme: AppTheme.darkClassic,
userId: currentUserId,
),
TradeExecutionCockpit.show(
context,
trade: trade,
defaultSymbol: trade.symbol.isNotEmpty ? trade.symbol : trade.isin,
isActive: isActive,
onAccept: (dto) {
tradeBloc.add(AcceptTradeProposalEvent(dto));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(isActive ? 'Einstellungen für ${trade.symbol} gespeichert!' : 'Trade für ${trade.symbol} eröffnet!'),
backgroundColor: AppTheme.primaryEmerald,
behavior: SnackBarBehavior.floating,
),
);
},
);
}
if (result != null && mounted) {
context.read<TradeBloc>().add(AcceptTradeProposalEvent(result));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Trade wird in dein Portfolio übernommen...'),
backgroundColor: AppTheme.primaryEmerald,
),
);
}
void _handleCloseTrade(BuildContext context, TradeModel trade) {
final tradeBloc = context.read<TradeBloc>();
TradeClosingCockpit.show(
context,
trade: trade,
defaultSymbol: trade.symbol.isNotEmpty ? trade.symbol : trade.isin,
onClose: (CloseTradeRequestDto dto) {
tradeBloc.add(CloseTrade(trade.id, dto: dto));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Position ${trade.symbol} geschlossen! Realisierter Verkaufskurs: €${dto.userExitPrice.toStringAsFixed(2)}'),
backgroundColor: AppTheme.primaryEmerald,
behavior: SnackBarBehavior.floating,
),
);
},
);
}
@override
@@ -102,12 +118,12 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Live Portfolio & Trading',
'Live Portfolio & Trades',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white),
),
const SizedBox(height: 2),
Text(
'KI-Erkennungen, Vorschläge & Aktive Positionen',
'KI-Guardian Überwachung, Drift-Radar & Order-Cockpit',
style: TextStyle(color: AppTheme.textMuted, fontSize: 12),
),
],
@@ -211,11 +227,11 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
scrollDirection: Axis.horizontal,
child: Row(
children: [
_filterChip('Alle', allTrades.length),
_filterChip('Offen', activeTrades.length),
_filterChip('Vorschläge', proposals.length),
_filterChip('Abgelehnt', rejectedTrades.length),
_filterChip('Geschlossen', closedTrades.length),
_filterChip('Abgelehnt', rejectedTrades.length),
_filterChip('Alle', allTrades.length),
],
),
),
@@ -228,7 +244,7 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
children: [
Icon(Icons.inbox, size: 40, color: AppTheme.textMuted),
const SizedBox(height: 8),
Text('Keine Trades in der Kategorie "$_selectedFilter" gefunden.', style: TextStyle(color: AppTheme.textMuted, fontSize: 13)),
Text('Keine Trades in der Kategorie "$_selectedFilter" vorhanden.', style: TextStyle(color: AppTheme.textMuted, fontSize: 13)),
],
),
),
@@ -243,13 +259,8 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
return TradeCard(
trade: trade,
onAccept: () => _handleAcceptProposal(context, trade),
onSettings: () => _showTradeSettingsDialog(context, trade),
onClose: () {
context.read<TradeBloc>().add(CloseTrade(trade.id));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Position wird geschlossen...')),
);
},
onSettings: () => _handleAcceptProposal(context, trade, isActive: true),
onClose: () => _handleCloseTrade(context, trade),
);
},
),
@@ -312,24 +323,5 @@ class _TradesFeedScreenContentState extends State<_TradesFeedScreenContent> {
),
);
}
void _showTradeSettingsDialog(BuildContext context, TradeModel trade) {
final tradeBloc = context.read<TradeBloc>();
TradeExecutionDialog.show(
context,
trade: trade,
defaultSymbol: trade.symbol,
isActive: true,
onAccept: (dto) {
tradeBloc.add(AcceptTradeProposalEvent(dto));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Einstellungen für ${trade.symbol} gespeichert.'),
backgroundColor: AppTheme.primaryEmerald,
),
);
},
);
}
}